@grafana/ui
Version:
Grafana Components Library
235 lines (232 loc) • 8.97 kB
JavaScript
import { jsx, jsxs } from 'react/jsx-runtime';
import { css } from '@emotion/css';
import { memo, useState, useMemo, useCallback } from 'react';
import { extractFacetedLabels, getFieldDisplayName, getFieldSeriesColor, resolveFacetedFilterNames } from '@grafana/data';
import { t } from '@grafana/i18n';
import { AxisPlacement } from '@grafana/schema';
import { SeriesVisibilityChangeMode } from '../PanelChrome/types.mjs';
import { useTheme2, useStyles2 } from '../../themes/ThemeContext.mjs';
import { Button } from '../Button/Button.mjs';
import { IconButton } from '../IconButton/IconButton.mjs';
import '../PanelChrome/index.mjs';
import { Toggletip } from '../Toggletip/Toggletip.mjs';
import { VizLayout } from '../VizLayout/VizLayout.mjs';
import { FacetedLabelsFilter } from '../VizLegend/FacetedLabelsFilter.mjs';
import { VizLegend } from '../VizLegend/VizLegend.mjs';
import { getDisplayValuesForCalcs } from './utils.mjs';
import { usePanelContext } from '../PanelChrome/PanelContext.mjs';
"use strict";
function hasVisibleLegendSeries(config, data) {
return config.getSeries().some((s) => {
var _a, _b, _c;
const fieldIndex = s.props.dataFrameFieldIndex;
if (!fieldIndex) {
return false;
}
const field = (_a = data[fieldIndex.frameIndex]) == null ? void 0 : _a.fields[fieldIndex.fieldIndex];
if (!field || ((_c = (_b = field.config.custom) == null ? void 0 : _b.hideFrom) == null ? void 0 : _c.legend)) {
return false;
}
return true;
});
}
const PlotLegend = memo(function PlotLegend2({
data,
config,
placement,
calcs,
displayMode,
limit,
enableFacetedFilter = false,
facetedFilterPinned = false,
onPinnedToSidebarChange,
...vizLayoutLegendProps
}) {
const theme = useTheme2();
const styles = useStyles2(getPlotLegendStyles);
const { onToggleSeriesVisibility } = usePanelContext();
const [selectedLabels, setSelectedLabels] = useState({});
const facetedLabels = useMemo(
() => enableFacetedFilter && onToggleSeriesVisibility ? extractFacetedLabels(data) : {},
[enableFacetedFilter, onToggleSeriesVisibility, data]
);
const hasFacetedLabels = Object.keys(facetedLabels).length > 0;
const hasActiveFilters = Object.values(selectedLabels).some((v) => v.length > 0);
const legendItems = config.getSeries().map((s) => {
var _a, _b, _c, _d;
const seriesConfig = s.props;
const fieldIndex = seriesConfig.dataFrameFieldIndex;
const axisPlacement = config.getAxisPlacement(s.props.scaleKey);
if (!fieldIndex) {
return void 0;
}
const field = (_a = data[fieldIndex.frameIndex]) == null ? void 0 : _a.fields[fieldIndex.fieldIndex];
if (!field || ((_c = (_b = field.config.custom) == null ? void 0 : _b.hideFrom) == null ? void 0 : _c.legend)) {
return void 0;
}
const label = getFieldDisplayName(field, data[fieldIndex.frameIndex], data);
const scaleColor = getFieldSeriesColor(field, theme);
const seriesColor = scaleColor.color;
return {
disabled: !((_d = seriesConfig.show) != null ? _d : true),
fieldIndex,
color: seriesColor,
label,
yAxis: axisPlacement === AxisPlacement.Left || axisPlacement === AxisPlacement.Bottom ? 1 : 2,
getDisplayValues: () => getDisplayValuesForCalcs(calcs, field, theme),
getItemKey: () => `${label}-${fieldIndex.frameIndex}-${fieldIndex.fieldIndex}`,
lineStyle: seriesConfig.lineStyle
};
}).filter((i) => i !== void 0);
const legendHasPrecedence = useMemo(() => {
if (!hasActiveFilters) {
return false;
}
const expectedVisible = resolveFacetedFilterNames(data, selectedLabels, getFieldDisplayName);
if (!expectedVisible) {
return false;
}
const expectedSet = new Set(expectedVisible);
const actuallyVisible = new Set(legendItems.filter((item) => !item.disabled).map((item) => item.label));
if (expectedSet.size !== actuallyVisible.size) {
return true;
}
return !Array.from(expectedSet).every((name) => actuallyVisible.has(name));
}, [hasActiveFilters, data, selectedLabels, legendItems]);
const handleLabelsChange = useCallback(
(selected) => {
setSelectedLabels(selected);
const visibleNames = resolveFacetedFilterNames(data, selected, getFieldDisplayName);
onToggleSeriesVisibility == null ? void 0 : onToggleSeriesVisibility(visibleNames, SeriesVisibilityChangeMode.SetExactly);
},
[data, onToggleSeriesVisibility]
);
const handleClearFilters = useCallback(() => {
setSelectedLabels({});
onToggleSeriesVisibility == null ? void 0 : onToggleSeriesVisibility(null, SeriesVisibilityChangeMode.SetExactly);
}, [onToggleSeriesVisibility]);
const handleToggleFilterDock = useCallback(() => {
onPinnedToSidebarChange == null ? void 0 : onPinnedToSidebarChange(!facetedFilterPinned);
}, [onPinnedToSidebarChange, facetedFilterPinned]);
const facetedFilter = hasFacetedLabels ? /* @__PURE__ */ jsx(
FacetedLabelsFilter,
{
labels: facetedLabels,
selected: selectedLabels,
onChange: handleLabelsChange,
dimmed: legendHasPrecedence
}
) : null;
const filterToggle = facetedFilter ? /* @__PURE__ */ jsx(
Toggletip,
{
content: /* @__PURE__ */ jsxs("div", { className: styles.filterPopoverContent, children: [
facetedFilter,
/* @__PURE__ */ jsxs("div", { className: styles.filterPopoverFooter, children: [
/* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", icon: "gf-pin", onClick: handleToggleFilterDock, children: t("grafana-ui.viz-legend.pin-filter", "Pin to sidebar") }),
hasActiveFilters && /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", icon: "times", onClick: handleClearFilters, children: t("grafana-ui.viz-legend.clear-filters", "Clear all") })
] })
] }),
placement: "bottom-start",
fitContent: true,
dismissOnScroll: true,
children: /* @__PURE__ */ jsx(
IconButton,
{
name: "eye",
size: "md",
tooltip: t("grafana-ui.viz-legend.series-visibility", "Series visibility"),
variant: hasActiveFilters ? "primary" : void 0,
"data-testid": "faceted-labels-filter-toggle"
}
)
}
) : null;
const legend = /* @__PURE__ */ jsx(
VizLegend,
{
placement,
items: legendItems,
displayMode,
sortBy: vizLayoutLegendProps.sortBy,
sortDesc: vizLayoutLegendProps.sortDesc,
isSortable: true,
limit,
filterAction: !facetedFilterPinned ? filterToggle : void 0,
overflow: vizLayoutLegendProps.overflow
}
);
if (facetedFilterPinned && facetedFilter) {
return /* @__PURE__ */ jsx(VizLayout.Legend, { placement, ...vizLayoutLegendProps, children: /* @__PURE__ */ jsxs("div", { className: styles.legendWithFilter, children: [
/* @__PURE__ */ jsxs("div", { className: styles.filterContent, children: [
/* @__PURE__ */ jsxs("div", { className: styles.filterDockedActions, children: [
hasActiveFilters && /* @__PURE__ */ jsx(
IconButton,
{
name: "filter-minus",
size: "sm",
tooltip: t("grafana-ui.viz-legend.clear-filters", "Clear all"),
onClick: handleClearFilters
}
),
/* @__PURE__ */ jsx(
IconButton,
{
name: "times",
size: "sm",
tooltip: t("grafana-ui.viz-legend.unpin-sidebar", "Unpin"),
onClick: handleToggleFilterDock
}
)
] }),
facetedFilter
] }),
/* @__PURE__ */ jsx("div", { className: styles.legendContent, children: legend })
] }) });
}
return /* @__PURE__ */ jsx(VizLayout.Legend, { placement, ...vizLayoutLegendProps, children: legend });
});
PlotLegend.displayName = "PlotLegend";
const getPlotLegendStyles = (theme) => ({
legendWithFilter: css({
display: "flex",
width: "100%",
height: "100%",
gap: theme.spacing(1),
overflow: "hidden"
}),
legendContent: css({
flex: 1,
minWidth: 0,
overflow: "auto"
}),
filterContent: css({
position: "relative",
maxWidth: "50%",
overflow: "auto",
borderRight: `1px solid ${theme.colors.border.weak}`,
paddingRight: theme.spacing(1)
}),
filterDockedActions: css({
position: "absolute",
zIndex: 1,
right: theme.spacing(0.5),
top: theme.spacing(0.5),
display: "flex",
gap: theme.spacing(0.25),
color: theme.colors.text.secondary
}),
filterPopoverContent: css({
margin: theme.spacing(-3, -2),
maxHeight: 400,
overflow: "auto"
}),
filterPopoverFooter: css({
display: "flex",
gap: theme.spacing(1),
borderTop: `1px solid ${theme.colors.border.medium}`,
padding: theme.spacing(1)
})
});
export { PlotLegend, hasVisibleLegendSeries };
//# sourceMappingURL=PlotLegend.mjs.map