mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
PieChart: move hiding series to PanelContext (#33756)
This commit is contained in:
parent
8333c55bee
commit
9e2e7b66a1
@ -117,7 +117,7 @@ export function preparePlotConfigBuilder(
|
||||
colorMode,
|
||||
pathBuilder: config.drawBars,
|
||||
pointsBuilder: config.drawPoints,
|
||||
show: !customConfig.hideFrom?.graph,
|
||||
show: !customConfig.hideFrom?.viz,
|
||||
gradientMode: customConfig.gradientMode,
|
||||
thresholds: field.config.thresholds,
|
||||
|
||||
|
@ -1,14 +1,5 @@
|
||||
import { DataFrameFieldIndex, FieldMatcher } from '@grafana/data';
|
||||
|
||||
/**
|
||||
* Mode to describe if a legend is isolated/selected or being appended to an existing
|
||||
* series selection.
|
||||
* @alpha
|
||||
*/
|
||||
export enum GraphNGLegendEventMode {
|
||||
ToggleSelection = 'select',
|
||||
AppendToSelection = 'append',
|
||||
}
|
||||
import { SeriesVisibilityChangeMode } from '..';
|
||||
|
||||
/**
|
||||
* Event being triggered when the user interact with the Graph legend.
|
||||
@ -16,7 +7,7 @@ export enum GraphNGLegendEventMode {
|
||||
*/
|
||||
export interface GraphNGLegendEvent {
|
||||
fieldIndex: DataFrameFieldIndex;
|
||||
mode: GraphNGLegendEventMode;
|
||||
mode: SeriesVisibilityChangeMode;
|
||||
}
|
||||
|
||||
/** @alpha */
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { GraphNGLegendEventMode, XYFieldMatchers } from './types';
|
||||
import { XYFieldMatchers } from './types';
|
||||
import {
|
||||
ArrayVector,
|
||||
DataFrame,
|
||||
@ -18,13 +17,6 @@ export interface PrepConfigOpts {
|
||||
[prop: string]: any;
|
||||
}
|
||||
|
||||
export function mapMouseEventToMode(event: React.MouseEvent): GraphNGLegendEventMode {
|
||||
if (event.ctrlKey || event.metaKey || event.shiftKey) {
|
||||
return GraphNGLegendEventMode.AppendToSelection;
|
||||
}
|
||||
return GraphNGLegendEventMode.ToggleSelection;
|
||||
}
|
||||
|
||||
function applySpanNullsThresholds(frames: DataFrame[]) {
|
||||
for (const frame of frames) {
|
||||
let refField = frame.fields.find((field) => field.type === FieldType.time); // this doesnt need to be time, just any numeric/asc join field
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { EventBusSrv, EventBus } from '@grafana/data';
|
||||
import React from 'react';
|
||||
import { SeriesVisibilityChangeMode } from '.';
|
||||
|
||||
/** @alpha */
|
||||
export interface PanelContext {
|
||||
@ -11,6 +12,8 @@ export interface PanelContext {
|
||||
* @alpha -- experimental
|
||||
*/
|
||||
onSeriesColorChange?: (label: string, color: string) => void;
|
||||
|
||||
onToggleSeriesVisibility?: (label: string, mode: SeriesVisibilityChangeMode) => void;
|
||||
}
|
||||
|
||||
const PanelContextRoot = React.createContext<PanelContext>({
|
||||
|
@ -38,3 +38,5 @@ export {
|
||||
} from './ErrorIndicator';
|
||||
|
||||
export { usePanelContext, PanelContextProvider, PanelContext } from './PanelContext';
|
||||
|
||||
export * from './types';
|
||||
|
10
packages/grafana-ui/src/components/PanelChrome/types.ts
Normal file
10
packages/grafana-ui/src/components/PanelChrome/types.ts
Normal file
@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Mode to describe if a legend is isolated/selected or being appended to an existing
|
||||
* series selection.
|
||||
* @alpha
|
||||
*/
|
||||
|
||||
export enum SeriesVisibilityChangeMode {
|
||||
ToggleSelection = 'select',
|
||||
AppendToSelection = 'append',
|
||||
}
|
@ -35,6 +35,7 @@ import { getTooltipContainerStyles } from '../../themes/mixins';
|
||||
import { SeriesTable, SeriesTableRowProps, VizTooltipOptions } from '../VizTooltip';
|
||||
import { usePanelContext } from '../PanelChrome';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { SeriesVisibilityChangeBehavior } from '../VizLegend/types';
|
||||
|
||||
const defaultLegendOptions: PieChartLegendOptions = {
|
||||
displayMode: LegendDisplayMode.List,
|
||||
@ -94,31 +95,37 @@ function getLegend(props: PieChartProps, displayValues: FieldDisplay[]) {
|
||||
if (legendOptions.displayMode === LegendDisplayMode.Hidden) {
|
||||
return undefined;
|
||||
}
|
||||
const values = displayValues.map((v) => v.display);
|
||||
const total = values.reduce((acc, item) => item.numeric + acc, 0);
|
||||
const total = displayValues
|
||||
.filter((item) => {
|
||||
return !item.field.custom.hideFrom.viz;
|
||||
})
|
||||
.reduce((acc, item) => item.display.numeric + acc, 0);
|
||||
|
||||
const legendItems = values.map<VizLegendItem>((value, idx) => {
|
||||
const legendItems = displayValues.map<VizLegendItem>((value, idx) => {
|
||||
const hidden = value.field.custom.hideFrom.viz;
|
||||
const display = value.display;
|
||||
return {
|
||||
label: value.title ?? '',
|
||||
color: value.color ?? FALLBACK_COLOR,
|
||||
label: display.title ?? '',
|
||||
color: display.color ?? FALLBACK_COLOR,
|
||||
yAxis: 1,
|
||||
getItemKey: () => (value.title ?? '') + idx,
|
||||
disabled: hidden,
|
||||
getItemKey: () => (display.title ?? '') + idx,
|
||||
getDisplayValues: () => {
|
||||
const valuesToShow = legendOptions.values ?? [];
|
||||
let displayValues = [];
|
||||
|
||||
if (valuesToShow.includes(PieChartLegendValues.Value)) {
|
||||
displayValues.push({ numeric: value.numeric, text: formattedValueToString(value), title: 'Value' });
|
||||
displayValues.push({ numeric: display.numeric, text: formattedValueToString(display), title: 'Value' });
|
||||
}
|
||||
|
||||
if (valuesToShow.includes(PieChartLegendValues.Percent)) {
|
||||
const fractionOfTotal = value.numeric / total;
|
||||
const fractionOfTotal = hidden ? 0 : display.numeric / total;
|
||||
const percentOfTotal = fractionOfTotal * 100;
|
||||
|
||||
displayValues.push({
|
||||
numeric: fractionOfTotal,
|
||||
percent: percentOfTotal,
|
||||
text: percentOfTotal.toFixed(0) + '%',
|
||||
text: hidden ? '-' : percentOfTotal.toFixed(0) + '%',
|
||||
title: valuesToShow.length > 1 ? 'Percent' : undefined,
|
||||
});
|
||||
}
|
||||
@ -128,7 +135,14 @@ function getLegend(props: PieChartProps, displayValues: FieldDisplay[]) {
|
||||
};
|
||||
});
|
||||
|
||||
return <VizLegend items={legendItems} placement={legendOptions.placement} displayMode={legendOptions.displayMode} />;
|
||||
return (
|
||||
<VizLegend
|
||||
items={legendItems}
|
||||
seriesVisibilityChangeBehavior={SeriesVisibilityChangeBehavior.Hide}
|
||||
placement={legendOptions.placement}
|
||||
displayMode={legendOptions.displayMode}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function useSliceHighlightState() {
|
||||
@ -174,7 +188,11 @@ export const PieChartSvg: FC<PieChartSvgProps> = ({
|
||||
scroll: true,
|
||||
});
|
||||
|
||||
if (fieldDisplayValues.length < 0) {
|
||||
const filteredFieldDisplayValues = fieldDisplayValues.filter((dv) => {
|
||||
return !dv.field.custom.hideFrom.viz;
|
||||
});
|
||||
|
||||
if (filteredFieldDisplayValues.length < 0) {
|
||||
return <div>No data</div>;
|
||||
}
|
||||
|
||||
@ -186,10 +204,12 @@ export const PieChartSvg: FC<PieChartSvgProps> = ({
|
||||
|
||||
const showLabel = displayLabels.length > 0;
|
||||
const showTooltip = tooltipOptions.mode !== 'none' && tooltip.tooltipOpen;
|
||||
const total = fieldDisplayValues.reduce((acc, item) => item.display.numeric + acc, 0);
|
||||
const total = filteredFieldDisplayValues.reduce((acc, item) => item.display.numeric + acc, 0);
|
||||
const layout = getPieLayout(width, height, pieType);
|
||||
const colors = [
|
||||
...new Set(fieldDisplayValues.map((fieldDisplayValue) => fieldDisplayValue.display.color ?? FALLBACK_COLOR)),
|
||||
...new Set(
|
||||
filteredFieldDisplayValues.map((fieldDisplayValue) => fieldDisplayValue.display.color ?? FALLBACK_COLOR)
|
||||
),
|
||||
];
|
||||
|
||||
return (
|
||||
@ -213,7 +233,7 @@ export const PieChartSvg: FC<PieChartSvgProps> = ({
|
||||
);
|
||||
})}
|
||||
<Pie
|
||||
data={fieldDisplayValues}
|
||||
data={filteredFieldDisplayValues}
|
||||
pieValue={getValue}
|
||||
outerRadius={layout.outerRadius}
|
||||
innerRadius={layout.innerRadius}
|
||||
|
@ -161,7 +161,7 @@ export const preparePlotConfigBuilder: PrepConfig = ({ frame, theme, timeZone, g
|
||||
pointSize: customConfig.pointSize,
|
||||
pointColor: customConfig.pointColor ?? seriesColor,
|
||||
spanNulls: customConfig.spanNulls || false,
|
||||
show: !customConfig.hideFrom?.graph,
|
||||
show: !customConfig.hideFrom?.viz,
|
||||
gradientMode: customConfig.gradientMode,
|
||||
thresholds: config.thresholds,
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { GraphNGLegendEventMode, XYFieldMatchers } from '../GraphNG/types';
|
||||
import { XYFieldMatchers } from '../GraphNG/types';
|
||||
import {
|
||||
DataFrame,
|
||||
FieldColorModeId,
|
||||
@ -17,7 +17,7 @@ import { AxisPlacement, GraphGradientMode, ScaleDirection, ScaleOrientation } fr
|
||||
import { measureText } from '../../utils/measureText';
|
||||
import { PrepConfigOpts } from '../GraphNG/utils';
|
||||
|
||||
import { TimelineFieldConfig } from '../..';
|
||||
import { SeriesVisibilityChangeMode, TimelineFieldConfig } from '../..';
|
||||
import { BarValueVisibility, TimelineMode } from './types';
|
||||
|
||||
const defaultConfig: TimelineFieldConfig = {
|
||||
@ -26,11 +26,11 @@ const defaultConfig: TimelineFieldConfig = {
|
||||
gradientMode: GraphGradientMode.None,
|
||||
};
|
||||
|
||||
export function mapMouseEventToMode(event: React.MouseEvent): GraphNGLegendEventMode {
|
||||
export function mapMouseEventToMode(event: React.MouseEvent): SeriesVisibilityChangeMode {
|
||||
if (event.ctrlKey || event.metaKey || event.shiftKey) {
|
||||
return GraphNGLegendEventMode.AppendToSelection;
|
||||
return SeriesVisibilityChangeMode.AppendToSelection;
|
||||
}
|
||||
return GraphNGLegendEventMode.ToggleSelection;
|
||||
return SeriesVisibilityChangeMode.ToggleSelection;
|
||||
}
|
||||
|
||||
export function preparePlotFrame(data: DataFrame[], dimFields: XYFieldMatchers) {
|
||||
@ -186,7 +186,7 @@ export const preparePlotConfigBuilder: PrepConfig = ({
|
||||
//colorMode,
|
||||
fillOpacity,
|
||||
theme,
|
||||
show: !customConfig.hideFrom?.graph,
|
||||
show: !customConfig.hideFrom?.viz,
|
||||
thresholds: config.thresholds,
|
||||
|
||||
// The following properties are not used in the uPlot config, but are utilized as transport for legend config
|
||||
|
@ -1,10 +1,11 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { LegendProps, VizLegendItem } from './types';
|
||||
import { LegendProps, SeriesVisibilityChangeBehavior, VizLegendItem } from './types';
|
||||
import { LegendDisplayMode } from './models.gen';
|
||||
import { VizLegendTable } from './VizLegendTable';
|
||||
import { VizLegendList } from './VizLegendList';
|
||||
import { DataHoverClearEvent, DataHoverEvent } from '@grafana/data';
|
||||
import { usePanelContext } from '../PanelChrome';
|
||||
import { SeriesVisibilityChangeMode, usePanelContext } from '../PanelChrome';
|
||||
import { mapMouseEventToMode } from './utils';
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -13,13 +14,14 @@ export const VizLegend: React.FunctionComponent<LegendProps> = ({
|
||||
items,
|
||||
displayMode,
|
||||
sortBy: sortKey,
|
||||
seriesVisibilityChangeBehavior = SeriesVisibilityChangeBehavior.Isolate,
|
||||
sortDesc,
|
||||
onToggleSort,
|
||||
onLabelClick,
|
||||
onToggleSort,
|
||||
placement,
|
||||
className,
|
||||
}) => {
|
||||
const { eventBus } = usePanelContext();
|
||||
const { eventBus, onToggleSeriesVisibility } = usePanelContext();
|
||||
|
||||
const onMouseEnter = useCallback(
|
||||
(item: VizLegendItem, event: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||
@ -51,6 +53,23 @@ export const VizLegend: React.FunctionComponent<LegendProps> = ({
|
||||
[eventBus]
|
||||
);
|
||||
|
||||
const onLegendLabelClick = useCallback(
|
||||
(item: VizLegendItem, event: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||
if (onLabelClick) {
|
||||
onLabelClick(item, event);
|
||||
}
|
||||
if (onToggleSeriesVisibility) {
|
||||
onToggleSeriesVisibility(
|
||||
item.label,
|
||||
seriesVisibilityChangeBehavior === SeriesVisibilityChangeBehavior.Hide
|
||||
? SeriesVisibilityChangeMode.AppendToSelection
|
||||
: mapMouseEventToMode(event)
|
||||
);
|
||||
}
|
||||
},
|
||||
[onToggleSeriesVisibility, onLabelClick, seriesVisibilityChangeBehavior]
|
||||
);
|
||||
|
||||
switch (displayMode) {
|
||||
case LegendDisplayMode.Table:
|
||||
return (
|
||||
@ -60,7 +79,7 @@ export const VizLegend: React.FunctionComponent<LegendProps> = ({
|
||||
placement={placement}
|
||||
sortBy={sortKey}
|
||||
sortDesc={sortDesc}
|
||||
onLabelClick={onLabelClick}
|
||||
onLabelClick={onLegendLabelClick}
|
||||
onToggleSort={onToggleSort}
|
||||
onLabelMouseEnter={onMouseEnter}
|
||||
onLabelMouseOut={onMouseOut}
|
||||
@ -74,7 +93,7 @@ export const VizLegend: React.FunctionComponent<LegendProps> = ({
|
||||
placement={placement}
|
||||
onLabelMouseEnter={onMouseEnter}
|
||||
onLabelMouseOut={onMouseOut}
|
||||
onLabelClick={onLabelClick}
|
||||
onLabelClick={onLegendLabelClick}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
|
@ -15,9 +15,9 @@ export interface Props extends VizLegendBaseProps {}
|
||||
export const VizLegendList: React.FunctionComponent<Props> = ({
|
||||
items,
|
||||
itemRenderer,
|
||||
onLabelClick,
|
||||
onLabelMouseEnter,
|
||||
onLabelMouseOut,
|
||||
onLabelClick,
|
||||
placement,
|
||||
className,
|
||||
}) => {
|
||||
|
@ -2,12 +2,18 @@ import { DataFrameFieldIndex, DisplayValue } from '@grafana/data';
|
||||
import React from 'react';
|
||||
import { LegendDisplayMode, LegendPlacement } from './models.gen';
|
||||
|
||||
export enum SeriesVisibilityChangeBehavior {
|
||||
Isolate,
|
||||
Hide,
|
||||
}
|
||||
|
||||
export interface VizLegendBaseProps {
|
||||
placement: LegendPlacement;
|
||||
className?: string;
|
||||
items: VizLegendItem[];
|
||||
itemRenderer?: (item: VizLegendItem, index: number) => JSX.Element;
|
||||
seriesVisibilityChangeBehavior?: SeriesVisibilityChangeBehavior;
|
||||
onLabelClick?: (item: VizLegendItem, event: React.MouseEvent<HTMLElement>) => void;
|
||||
itemRenderer?: (item: VizLegendItem, index: number) => JSX.Element;
|
||||
onLabelMouseEnter?: (item: VizLegendItem, event: React.MouseEvent<HTMLElement>) => void;
|
||||
onLabelMouseOut?: (item: VizLegendItem, event: React.MouseEvent<HTMLElement>) => void;
|
||||
}
|
||||
|
8
packages/grafana-ui/src/components/VizLegend/utils.ts
Normal file
8
packages/grafana-ui/src/components/VizLegend/utils.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { SeriesVisibilityChangeMode } from '..';
|
||||
|
||||
export function mapMouseEventToMode(event: React.MouseEvent): SeriesVisibilityChangeMode {
|
||||
if (event.ctrlKey || event.metaKey || event.shiftKey) {
|
||||
return SeriesVisibilityChangeMode.AppendToSelection;
|
||||
}
|
||||
return SeriesVisibilityChangeMode.ToggleSelection;
|
||||
}
|
@ -247,6 +247,7 @@ export { BarChart } from './BarChart/BarChart';
|
||||
export { TimelineChart } from './Timeline/TimelineChart';
|
||||
export { BarChartOptions, BarValueVisibility, BarChartFieldConfig } from './BarChart/types';
|
||||
export { TimelineOptions, TimelineFieldConfig } from './Timeline/types';
|
||||
export { GraphNGLegendEvent, GraphNGLegendEventMode } from './GraphNG/types';
|
||||
export { GraphNGLegendEvent } from './GraphNG/types';
|
||||
export * from './PanelChrome/types';
|
||||
export * from './NodeGraph';
|
||||
export { EmotionPerfTest } from './ThemeDemos/EmotionPerfTest';
|
||||
|
@ -5,9 +5,9 @@ import { VizLegendItem } from '../VizLegend/types';
|
||||
import { VizLegendOptions } from '../VizLegend/models.gen';
|
||||
import { AxisPlacement } from './config';
|
||||
import { VizLayout, VizLayoutLegendProps } from '../VizLayout/VizLayout';
|
||||
import { mapMouseEventToMode } from '../GraphNG/utils';
|
||||
import { VizLegend } from '../VizLegend/VizLegend';
|
||||
import { GraphNGLegendEvent } from '..';
|
||||
import { mapMouseEventToMode } from '../VizLegend/utils';
|
||||
|
||||
const defaultFormatter = (v: any) => (v == null ? '-' : v.toFixed(1));
|
||||
|
||||
|
@ -165,7 +165,7 @@ export interface AxisConfig {
|
||||
export interface HideSeriesConfig {
|
||||
tooltip: boolean;
|
||||
legend: boolean;
|
||||
graph: boolean;
|
||||
viz: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -233,10 +233,10 @@ describe('preparePlotData', () => {
|
||||
{
|
||||
name: 'a',
|
||||
values: [-10, 20, 10],
|
||||
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackA' }, hideFrom: { graph: true } } },
|
||||
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackA' }, hideFrom: { viz: true } } },
|
||||
},
|
||||
{
|
||||
// Will ignore a series as stacking base as it's hidden from graph
|
||||
// Will ignore a series as stacking base as it's hidden from viz
|
||||
name: 'b',
|
||||
values: [10, 10, 10],
|
||||
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackA' } } },
|
||||
@ -249,10 +249,10 @@ describe('preparePlotData', () => {
|
||||
{
|
||||
name: 'e',
|
||||
values: [1, 2, 3],
|
||||
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackB' }, hideFrom: { graph: true } } },
|
||||
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackB' }, hideFrom: { viz: true } } },
|
||||
},
|
||||
{
|
||||
// Will ignore e series as stacking base as it's hidden from graph
|
||||
// Will ignore e series as stacking base as it's hidden from viz
|
||||
name: 'f',
|
||||
values: [1, 2, 3],
|
||||
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackB' } } },
|
||||
|
@ -96,7 +96,7 @@ export function collectStackingGroups(f: Field, groups: Map<string, number[]>, s
|
||||
if (
|
||||
customConfig.stacking?.mode !== StackingMode.None &&
|
||||
customConfig.stacking?.group &&
|
||||
!customConfig.hideFrom?.graph
|
||||
!customConfig.hideFrom?.viz
|
||||
) {
|
||||
if (!groups.has(customConfig.stacking.group)) {
|
||||
groups.set(customConfig.stacking.group, [seriesIdx]);
|
||||
|
@ -4,7 +4,7 @@ import classNames from 'classnames';
|
||||
import { Subscription } from 'rxjs';
|
||||
// Components
|
||||
import { PanelHeader } from './PanelHeader/PanelHeader';
|
||||
import { ErrorBoundary, PanelContextProvider, PanelContext } from '@grafana/ui';
|
||||
import { ErrorBoundary, PanelContextProvider, PanelContext, SeriesVisibilityChangeMode } from '@grafana/ui';
|
||||
// Utils & Services
|
||||
import { getTimeSrv, TimeSrv } from '../services/TimeSrv';
|
||||
import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel';
|
||||
@ -30,6 +30,7 @@ import { selectors } from '@grafana/e2e-selectors';
|
||||
import { loadSnapshotData } from '../utils/loadSnapshotData';
|
||||
import { RefreshEvent, RenderEvent } from 'app/types/events';
|
||||
import { changeSeriesColorConfigFactory } from 'app/plugins/panel/timeseries/overrides/colorSeriesConfigFactory';
|
||||
import { seriesVisibilityConfigFactory } from './SeriesVisibilityConfigFactory';
|
||||
|
||||
const DEFAULT_PLUGIN_ERROR = 'Error in plugin';
|
||||
|
||||
@ -77,6 +78,7 @@ export class PanelChrome extends Component<Props, State> {
|
||||
context: {
|
||||
eventBus,
|
||||
onSeriesColorChange: this.onSeriesColorChange,
|
||||
onToggleSeriesVisibility: this.onSeriesVisibilityChange,
|
||||
},
|
||||
data: this.getInitialPanelDataState(),
|
||||
};
|
||||
@ -86,6 +88,12 @@ export class PanelChrome extends Component<Props, State> {
|
||||
this.onFieldConfigChange(changeSeriesColorConfigFactory(label, color, this.props.panel.fieldConfig));
|
||||
};
|
||||
|
||||
onSeriesVisibilityChange = (label: string, mode: SeriesVisibilityChangeMode) => {
|
||||
this.onFieldConfigChange(
|
||||
seriesVisibilityConfigFactory(label, mode, this.props.panel.fieldConfig, this.state.data.series)
|
||||
);
|
||||
};
|
||||
|
||||
getInitialPanelDataState(): PanelData {
|
||||
return {
|
||||
state: LoadingState.NotStarted,
|
||||
|
@ -0,0 +1,171 @@
|
||||
import {
|
||||
ByNamesMatcherMode,
|
||||
DataFrame,
|
||||
DynamicConfigValue,
|
||||
FieldConfigSource,
|
||||
FieldMatcherID,
|
||||
FieldType,
|
||||
getFieldDisplayName,
|
||||
isSystemOverrideWithRef,
|
||||
SystemConfigOverrideRule,
|
||||
} from '@grafana/data';
|
||||
import { SeriesVisibilityChangeMode } from '@grafana/ui';
|
||||
|
||||
const displayOverrideRef = 'hideSeriesFrom';
|
||||
const isHideSeriesOverride = isSystemOverrideWithRef(displayOverrideRef);
|
||||
|
||||
export function seriesVisibilityConfigFactory(
|
||||
label: string,
|
||||
mode: SeriesVisibilityChangeMode,
|
||||
fieldConfig: FieldConfigSource,
|
||||
data: DataFrame[]
|
||||
) {
|
||||
const { overrides } = fieldConfig;
|
||||
|
||||
const displayName = label;
|
||||
const currentIndex = overrides.findIndex(isHideSeriesOverride);
|
||||
|
||||
if (currentIndex < 0) {
|
||||
if (mode === SeriesVisibilityChangeMode.ToggleSelection) {
|
||||
const override = createOverride([displayName]);
|
||||
|
||||
return {
|
||||
...fieldConfig,
|
||||
overrides: [override, ...fieldConfig.overrides],
|
||||
};
|
||||
}
|
||||
|
||||
const displayNames = getDisplayNames(data, displayName);
|
||||
const override = createOverride(displayNames);
|
||||
|
||||
return {
|
||||
...fieldConfig,
|
||||
overrides: [override, ...fieldConfig.overrides],
|
||||
};
|
||||
}
|
||||
|
||||
const overridesCopy = Array.from(overrides);
|
||||
const [current] = overridesCopy.splice(currentIndex, 1) as SystemConfigOverrideRule[];
|
||||
|
||||
if (mode === SeriesVisibilityChangeMode.ToggleSelection) {
|
||||
const existing = getExistingDisplayNames(current);
|
||||
|
||||
if (existing[0] === displayName && existing.length === 1) {
|
||||
return {
|
||||
...fieldConfig,
|
||||
overrides: overridesCopy,
|
||||
};
|
||||
}
|
||||
|
||||
const override = createOverride([displayName]);
|
||||
|
||||
return {
|
||||
...fieldConfig,
|
||||
overrides: [override, ...overridesCopy],
|
||||
};
|
||||
}
|
||||
|
||||
const override = createExtendedOverride(current, displayName);
|
||||
|
||||
if (allFieldsAreExcluded(override, data)) {
|
||||
return {
|
||||
...fieldConfig,
|
||||
overrides: overridesCopy,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...fieldConfig,
|
||||
overrides: [override, ...overridesCopy],
|
||||
};
|
||||
}
|
||||
|
||||
function createOverride(
|
||||
names: string[],
|
||||
mode = ByNamesMatcherMode.exclude,
|
||||
property?: DynamicConfigValue
|
||||
): SystemConfigOverrideRule {
|
||||
property = property ?? {
|
||||
id: 'custom.hideFrom',
|
||||
value: {
|
||||
viz: true,
|
||||
legend: false,
|
||||
tooltip: false,
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
__systemRef: displayOverrideRef,
|
||||
matcher: {
|
||||
id: FieldMatcherID.byNames,
|
||||
options: {
|
||||
mode: mode,
|
||||
names: names,
|
||||
prefix: mode === ByNamesMatcherMode.exclude ? 'All except:' : undefined,
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
...property,
|
||||
value: {
|
||||
viz: true,
|
||||
legend: false,
|
||||
tooltip: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const createExtendedOverride = (
|
||||
current: SystemConfigOverrideRule,
|
||||
displayName: string,
|
||||
mode = ByNamesMatcherMode.exclude
|
||||
): SystemConfigOverrideRule => {
|
||||
const property = current.properties.find((p) => p.id === 'custom.hideFrom');
|
||||
const existing = getExistingDisplayNames(current);
|
||||
const index = existing.findIndex((name) => name === displayName);
|
||||
|
||||
if (index < 0) {
|
||||
existing.push(displayName);
|
||||
} else {
|
||||
existing.splice(index, 1);
|
||||
}
|
||||
|
||||
return createOverride(existing, mode, property);
|
||||
};
|
||||
|
||||
const getExistingDisplayNames = (rule: SystemConfigOverrideRule): string[] => {
|
||||
const names = rule.matcher.options?.names;
|
||||
if (!Array.isArray(names)) {
|
||||
return [];
|
||||
}
|
||||
return names;
|
||||
};
|
||||
|
||||
const allFieldsAreExcluded = (override: SystemConfigOverrideRule, data: DataFrame[]): boolean => {
|
||||
return getExistingDisplayNames(override).length === getDisplayNames(data).length;
|
||||
};
|
||||
|
||||
const getDisplayNames = (data: DataFrame[], excludeName?: string): string[] => {
|
||||
const unique = new Set<string>();
|
||||
|
||||
for (const frame of data) {
|
||||
for (const field of frame.fields) {
|
||||
if (field.type !== FieldType.number) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const name = getFieldDisplayName(field, frame, data);
|
||||
|
||||
if (name === excludeName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unique.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(unique);
|
||||
};
|
@ -10,7 +10,6 @@ export const PieChartPanel: React.FC<Props> = ({
|
||||
height,
|
||||
options,
|
||||
data,
|
||||
onFieldConfigChange,
|
||||
replaceVariables,
|
||||
fieldConfig,
|
||||
timeZone,
|
||||
|
@ -3,6 +3,7 @@ import { PieChartPanel } from './PieChartPanel';
|
||||
import { PieChartOptions } from './types';
|
||||
import { LegendDisplayMode, PieChartType, PieChartLabels, PieChartLegendValues } from '@grafana/ui';
|
||||
import { PieChartPanelChangedHandler } from './migrations';
|
||||
import { addHideFrom } from '../timeseries/config';
|
||||
|
||||
export const plugin = new PanelPlugin<PieChartOptions>(PieChartPanel)
|
||||
.setPanelChangeHandler(PieChartPanelChangedHandler)
|
||||
@ -20,6 +21,9 @@ export const plugin = new PanelPlugin<PieChartOptions>(PieChartPanel)
|
||||
},
|
||||
},
|
||||
},
|
||||
useCustomConfig: (builder) => {
|
||||
addHideFrom(builder);
|
||||
},
|
||||
})
|
||||
.setPanelOptions((builder) => {
|
||||
builder
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { Field, PanelProps } from '@grafana/data';
|
||||
import { TimeSeries, GraphNGLegendEvent, TooltipPlugin, ZoomPlugin } from '@grafana/ui';
|
||||
import { TimeSeries, TooltipPlugin, ZoomPlugin } from '@grafana/ui';
|
||||
import { getFieldLinksForExplore } from 'app/features/explore/utils/links';
|
||||
import React, { useCallback } from 'react';
|
||||
import { hideSeriesConfigFactory } from './overrides/hideSeriesConfigFactory';
|
||||
import React from 'react';
|
||||
import { AnnotationsPlugin } from './plugins/AnnotationsPlugin';
|
||||
import { ContextMenuPlugin } from './plugins/ContextMenuPlugin';
|
||||
import { ExemplarsPlugin } from './plugins/ExemplarsPlugin';
|
||||
@ -17,18 +16,9 @@ export const TimeSeriesPanel: React.FC<TimeSeriesPanelProps> = ({
|
||||
width,
|
||||
height,
|
||||
options,
|
||||
fieldConfig,
|
||||
onChangeTimeRange,
|
||||
onFieldConfigChange,
|
||||
replaceVariables,
|
||||
}) => {
|
||||
const onLegendClick = useCallback(
|
||||
(event: GraphNGLegendEvent) => {
|
||||
onFieldConfigChange(hideSeriesConfigFactory(event, fieldConfig, data.series));
|
||||
},
|
||||
[fieldConfig, onFieldConfigChange, data.series]
|
||||
);
|
||||
|
||||
const getFieldLinks = (field: Field, rowIndex: number) => {
|
||||
return getFieldLinksForExplore({ field, rowIndex, range: timeRange });
|
||||
};
|
||||
@ -50,7 +40,6 @@ export const TimeSeriesPanel: React.FC<TimeSeriesPanelProps> = ({
|
||||
width={width}
|
||||
height={height}
|
||||
legend={options.legend}
|
||||
onLegendClick={onLegendClick}
|
||||
>
|
||||
{(config, alignedDataFrame) => {
|
||||
return (
|
||||
|
@ -6,13 +6,13 @@ import {
|
||||
FieldType,
|
||||
toDataFrame,
|
||||
} from '@grafana/data';
|
||||
import { GraphNGLegendEvent, GraphNGLegendEventMode } from '@grafana/ui';
|
||||
import { GraphNGLegendEvent, SeriesVisibilityChangeMode } from '@grafana/ui';
|
||||
import { hideSeriesConfigFactory } from './hideSeriesConfigFactory';
|
||||
|
||||
describe('hideSeriesConfigFactory', () => {
|
||||
it('should create config override matching one series', () => {
|
||||
const event: GraphNGLegendEvent = {
|
||||
mode: GraphNGLegendEventMode.ToggleSelection,
|
||||
mode: SeriesVisibilityChangeMode.ToggleSelection,
|
||||
fieldIndex: {
|
||||
frameIndex: 0,
|
||||
fieldIndex: 1,
|
||||
@ -43,7 +43,7 @@ describe('hideSeriesConfigFactory', () => {
|
||||
|
||||
it('should create config override matching one series if selected with others', () => {
|
||||
const event: GraphNGLegendEvent = {
|
||||
mode: GraphNGLegendEventMode.ToggleSelection,
|
||||
mode: SeriesVisibilityChangeMode.ToggleSelection,
|
||||
fieldIndex: {
|
||||
frameIndex: 0,
|
||||
fieldIndex: 1,
|
||||
@ -86,7 +86,7 @@ describe('hideSeriesConfigFactory', () => {
|
||||
|
||||
it('should create config override that append series to existing override', () => {
|
||||
const event: GraphNGLegendEvent = {
|
||||
mode: GraphNGLegendEventMode.AppendToSelection,
|
||||
mode: SeriesVisibilityChangeMode.AppendToSelection,
|
||||
fieldIndex: {
|
||||
frameIndex: 1,
|
||||
fieldIndex: 1,
|
||||
@ -129,7 +129,7 @@ describe('hideSeriesConfigFactory', () => {
|
||||
|
||||
it('should create config override that hides all series if appending only existing series', () => {
|
||||
const event: GraphNGLegendEvent = {
|
||||
mode: GraphNGLegendEventMode.AppendToSelection,
|
||||
mode: SeriesVisibilityChangeMode.AppendToSelection,
|
||||
fieldIndex: {
|
||||
frameIndex: 0,
|
||||
fieldIndex: 1,
|
||||
@ -166,7 +166,7 @@ describe('hideSeriesConfigFactory', () => {
|
||||
|
||||
it('should create config override that removes series if appending existing field', () => {
|
||||
const event: GraphNGLegendEvent = {
|
||||
mode: GraphNGLegendEventMode.AppendToSelection,
|
||||
mode: SeriesVisibilityChangeMode.AppendToSelection,
|
||||
fieldIndex: {
|
||||
frameIndex: 0,
|
||||
fieldIndex: 1,
|
||||
@ -203,7 +203,7 @@ describe('hideSeriesConfigFactory', () => {
|
||||
|
||||
it('should create config override replacing existing series', () => {
|
||||
const event: GraphNGLegendEvent = {
|
||||
mode: GraphNGLegendEventMode.ToggleSelection,
|
||||
mode: SeriesVisibilityChangeMode.ToggleSelection,
|
||||
fieldIndex: {
|
||||
frameIndex: 1,
|
||||
fieldIndex: 1,
|
||||
@ -240,7 +240,7 @@ describe('hideSeriesConfigFactory', () => {
|
||||
|
||||
it('should create config override removing existing series', () => {
|
||||
const event: GraphNGLegendEvent = {
|
||||
mode: GraphNGLegendEventMode.ToggleSelection,
|
||||
mode: SeriesVisibilityChangeMode.ToggleSelection,
|
||||
fieldIndex: {
|
||||
frameIndex: 0,
|
||||
fieldIndex: 1,
|
||||
@ -277,7 +277,7 @@ describe('hideSeriesConfigFactory', () => {
|
||||
|
||||
it('should remove override if all fields are appended', () => {
|
||||
const event: GraphNGLegendEvent = {
|
||||
mode: GraphNGLegendEventMode.AppendToSelection,
|
||||
mode: SeriesVisibilityChangeMode.AppendToSelection,
|
||||
fieldIndex: {
|
||||
frameIndex: 1,
|
||||
fieldIndex: 1,
|
||||
@ -314,7 +314,7 @@ describe('hideSeriesConfigFactory', () => {
|
||||
|
||||
it('should create config override hiding appended series if no previous override exists', () => {
|
||||
const event: GraphNGLegendEvent = {
|
||||
mode: GraphNGLegendEventMode.AppendToSelection,
|
||||
mode: SeriesVisibilityChangeMode.AppendToSelection,
|
||||
fieldIndex: {
|
||||
frameIndex: 0,
|
||||
fieldIndex: 1,
|
||||
@ -357,7 +357,7 @@ describe('hideSeriesConfigFactory', () => {
|
||||
|
||||
it('should return existing override if invalid index is passed', () => {
|
||||
const event: GraphNGLegendEvent = {
|
||||
mode: GraphNGLegendEventMode.ToggleSelection,
|
||||
mode: SeriesVisibilityChangeMode.ToggleSelection,
|
||||
fieldIndex: {
|
||||
frameIndex: 4,
|
||||
fieldIndex: 1,
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
isSystemOverrideWithRef,
|
||||
SystemConfigOverrideRule,
|
||||
} from '@grafana/data';
|
||||
import { GraphNGLegendEvent, GraphNGLegendEventMode } from '@grafana/ui';
|
||||
import { GraphNGLegendEvent, SeriesVisibilityChangeMode } from '@grafana/ui';
|
||||
|
||||
const displayOverrideRef = 'hideSeriesFrom';
|
||||
const isHideSeriesOverride = isSystemOverrideWithRef(displayOverrideRef);
|
||||
@ -38,7 +38,7 @@ export const hideSeriesConfigFactory = (
|
||||
const currentIndex = overrides.findIndex(isHideSeriesOverride);
|
||||
|
||||
if (currentIndex < 0) {
|
||||
if (mode === GraphNGLegendEventMode.ToggleSelection) {
|
||||
if (mode === SeriesVisibilityChangeMode.ToggleSelection) {
|
||||
const override = createOverride([displayName]);
|
||||
|
||||
return {
|
||||
@ -59,7 +59,7 @@ export const hideSeriesConfigFactory = (
|
||||
const overridesCopy = Array.from(overrides);
|
||||
const [current] = overridesCopy.splice(currentIndex, 1) as SystemConfigOverrideRule[];
|
||||
|
||||
if (mode === GraphNGLegendEventMode.ToggleSelection) {
|
||||
if (mode === SeriesVisibilityChangeMode.ToggleSelection) {
|
||||
const existing = getExistingDisplayNames(current);
|
||||
|
||||
if (existing[0] === displayName && existing.length === 1) {
|
||||
|
Loading…
Reference in New Issue
Block a user