mirror of https://github.com/grafana/grafana.git
				
				
				
			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'; | ||||
|  |  | |||
|  | @ -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; | ||||
| } | ||||
|  |  | |||
|  | @ -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