mirror of https://github.com/grafana/grafana.git
				
				
				
			TimeSeries: Add per-axis grid visibility toggle (#38502)
* Allow grid lines visibility control to XYChart and TimeSeries * Move grid lines config to field config (axis) * Fix merge * Auto grid mode * Fix ts * Align naming * Update packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.test.ts Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com> * Update packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.test.ts Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com> * remove 'grid' from props diff array since field.config.custom.* is handled by structureRev diffing Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com> Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
This commit is contained in:
		
							parent
							
								
									b5e4a0a39a
								
							
						
					
					
						commit
						1be53b4f3b
					
				|  | @ -144,6 +144,7 @@ export interface AxisConfig { | |||
|   axisWidth?: number; // pixels ideally auto?
 | ||||
|   axisSoftMin?: number; | ||||
|   axisSoftMax?: number; | ||||
|   axisGridShow?: boolean; | ||||
|   scaleDistribution?: ScaleDistributionConfig; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -12,7 +12,6 @@ import { | |||
|   TimeZone, | ||||
| } from '@grafana/data'; | ||||
| import { preparePlotFrame as defaultPreparePlotFrame } from './utils'; | ||||
| 
 | ||||
| import { VizLegendOptions } from '@grafana/schema'; | ||||
| import { PanelContext, PanelContextRoot } from '../PanelChrome/PanelContext'; | ||||
| import { Subscription } from 'rxjs'; | ||||
|  |  | |||
|  | @ -19,7 +19,16 @@ export class UnthemedTimeSeries extends React.Component<TimeSeriesProps> { | |||
|   prepConfig = (alignedFrame: DataFrame, allFrames: DataFrame[], getTimeRange: () => TimeRange) => { | ||||
|     const { eventBus, sync } = this.context; | ||||
|     const { theme, timeZone } = this.props; | ||||
|     return preparePlotConfigBuilder({ frame: alignedFrame, theme, timeZone, getTimeRange, eventBus, sync, allFrames }); | ||||
| 
 | ||||
|     return preparePlotConfigBuilder({ | ||||
|       frame: alignedFrame, | ||||
|       theme, | ||||
|       timeZone, | ||||
|       getTimeRange, | ||||
|       eventBus, | ||||
|       sync, | ||||
|       allFrames, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   renderLegend = (config: UPlotConfigBuilder) => { | ||||
|  |  | |||
|  | @ -79,6 +79,7 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{ sync: DashboardCursor | |||
|       placement: AxisPlacement.Bottom, | ||||
|       timeZone, | ||||
|       theme, | ||||
|       grid: { show: xField.config.custom?.axisGridShow }, | ||||
|     }); | ||||
|   } else { | ||||
|     // Not time!
 | ||||
|  | @ -96,6 +97,7 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{ sync: DashboardCursor | |||
|       scaleKey: xScaleKey, | ||||
|       placement: AxisPlacement.Bottom, | ||||
|       theme, | ||||
|       grid: { show: xField.config.custom?.axisGridShow }, | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|  | @ -147,6 +149,7 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{ sync: DashboardCursor | |||
|         placement: customConfig.axisPlacement ?? AxisPlacement.Auto, | ||||
|         formatValue: (v) => formattedValueToString(fmt(v)), | ||||
|         theme, | ||||
|         grid: { show: customConfig.axisGridShow }, | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ export interface AxisProps { | |||
|   size?: number | null; | ||||
|   gap?: number; | ||||
|   placement?: AxisPlacement; | ||||
|   grid?: boolean; | ||||
|   grid?: Axis.Grid; | ||||
|   ticks?: boolean; | ||||
|   formatValue?: (v: any) => string; | ||||
|   incrs?: Axis.Incrs; | ||||
|  | @ -43,7 +43,7 @@ export class UPlotAxisBuilder extends PlotConfigBuilder<AxisProps, Axis> { | |||
|       label, | ||||
|       show = true, | ||||
|       placement = AxisPlacement.Auto, | ||||
|       grid = true, | ||||
|       grid = { show: true }, | ||||
|       ticks = true, | ||||
|       gap = 5, | ||||
|       formatValue, | ||||
|  | @ -74,7 +74,7 @@ export class UPlotAxisBuilder extends PlotConfigBuilder<AxisProps, Axis> { | |||
|       labelGap: 0, | ||||
| 
 | ||||
|       grid: { | ||||
|         show: grid, | ||||
|         show: grid.show, | ||||
|         stroke: gridColor, | ||||
|         width: 1 / devicePixelRatio, | ||||
|       }, | ||||
|  |  | |||
|  | @ -329,7 +329,7 @@ describe('UPlotConfigBuilder', () => { | |||
|       placement: AxisPlacement.Bottom, | ||||
|       isTime: false, | ||||
|       formatValue: () => 'test value', | ||||
|       grid: false, | ||||
|       grid: { show: false }, | ||||
|       show: true, | ||||
|       theme: darkTheme, | ||||
|       values: [], | ||||
|  | @ -411,7 +411,6 @@ describe('UPlotConfigBuilder', () => { | |||
| 
 | ||||
|     expect(builder.getAxisPlacement('y1')).toBe(AxisPlacement.Left); | ||||
|     expect(builder.getAxisPlacement('y2')).toBe(AxisPlacement.Right); | ||||
|     expect(builder.getConfig().axes![1].grid!.show).toBe(false); | ||||
|   }); | ||||
| 
 | ||||
|   it('when fillColor is not set fill', () => { | ||||
|  | @ -709,4 +708,95 @@ describe('UPlotConfigBuilder', () => { | |||
|       expect(addHookFn).toHaveBeenCalledTimes(1); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   describe('Grid lines visibility', () => { | ||||
|     it('handles auto behaviour', () => { | ||||
|       const builder = new UPlotConfigBuilder(); | ||||
|       builder.addAxis({ | ||||
|         scaleKey: 'x', | ||||
|         placement: AxisPlacement.Bottom, | ||||
|         theme: darkTheme, | ||||
|       }); | ||||
| 
 | ||||
|       builder.addAxis({ | ||||
|         scaleKey: 'y1', | ||||
|         theme: darkTheme, | ||||
|       }); | ||||
| 
 | ||||
|       builder.addAxis({ | ||||
|         scaleKey: 'y2', | ||||
|         theme: darkTheme, | ||||
|       }); | ||||
|       builder.addAxis({ | ||||
|         scaleKey: 'y3', | ||||
|         theme: darkTheme, | ||||
|       }); | ||||
| 
 | ||||
|       const axesConfig = builder.getConfig().axes!; | ||||
| 
 | ||||
|       expect(axesConfig[0].grid!.show).toBe(true); | ||||
|       expect(axesConfig[1].grid!.show).toBe(true); | ||||
|       expect(axesConfig[2].grid!.show).toBe(false); | ||||
|       expect(axesConfig[3].grid!.show).toBe(false); | ||||
|     }); | ||||
| 
 | ||||
|     it('handles auto behaviour with explicite grid settings', () => { | ||||
|       const builder = new UPlotConfigBuilder(); | ||||
|       builder.addAxis({ | ||||
|         scaleKey: 'x', | ||||
|         placement: AxisPlacement.Bottom, | ||||
|         theme: darkTheme, | ||||
|       }); | ||||
| 
 | ||||
|       builder.addAxis({ | ||||
|         scaleKey: 'y1', | ||||
|         theme: darkTheme, | ||||
|       }); | ||||
| 
 | ||||
|       builder.addAxis({ | ||||
|         scaleKey: 'y2', | ||||
|         grid: { show: true }, | ||||
|         theme: darkTheme, | ||||
|       }); | ||||
|       builder.addAxis({ | ||||
|         scaleKey: 'y3', | ||||
|         theme: darkTheme, | ||||
|       }); | ||||
| 
 | ||||
|       const axesConfig = builder.getConfig().axes!; | ||||
| 
 | ||||
|       expect(axesConfig[0].grid!.show).toBe(true); | ||||
|       expect(axesConfig[1].grid!.show).toBe(true); | ||||
|       expect(axesConfig[2].grid!.show).toBe(true); | ||||
|       expect(axesConfig[3].grid!.show).toBe(false); | ||||
|     }); | ||||
| 
 | ||||
|     it('handles explicit grid settings', () => { | ||||
|       const builder = new UPlotConfigBuilder(); | ||||
|       builder.addAxis({ | ||||
|         scaleKey: 'x', | ||||
|         grid: { show: false }, | ||||
|         placement: AxisPlacement.Bottom, | ||||
|         theme: darkTheme, | ||||
|       }); | ||||
| 
 | ||||
|       builder.addAxis({ | ||||
|         scaleKey: 'y1', | ||||
|         grid: { show: false }, | ||||
|         theme: darkTheme, | ||||
|       }); | ||||
| 
 | ||||
|       builder.addAxis({ | ||||
|         scaleKey: 'y2', | ||||
|         grid: { show: true }, | ||||
|         theme: darkTheme, | ||||
|       }); | ||||
| 
 | ||||
|       const axesConfig = builder.getConfig().axes!; | ||||
| 
 | ||||
|       expect(axesConfig[0].grid!.show).toBe(false); | ||||
|       expect(axesConfig[1].grid!.show).toBe(false); | ||||
|       expect(axesConfig[2].grid!.show).toBe(true); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
|  |  | |||
|  | @ -42,7 +42,6 @@ export class UPlotConfigBuilder { | |||
|   private isStacking = false; | ||||
|   private select: uPlot.Select | undefined; | ||||
|   private hasLeftAxis = false; | ||||
|   private hasBottomAxis = false; | ||||
|   private hooks: Hooks.Arrays = {}; | ||||
|   private tz: string | undefined = undefined; | ||||
|   private sync = false; | ||||
|  | @ -80,7 +79,7 @@ export class UPlotConfigBuilder { | |||
| 
 | ||||
|   addAxis(props: AxisProps) { | ||||
|     props.placement = props.placement ?? AxisPlacement.Auto; | ||||
| 
 | ||||
|     props.grid = props.grid ?? {}; | ||||
|     if (this.axes[props.scaleKey]) { | ||||
|       this.axes[props.scaleKey].merge(props); | ||||
|       return; | ||||
|  | @ -91,17 +90,12 @@ export class UPlotConfigBuilder { | |||
|       props.placement = this.hasLeftAxis ? AxisPlacement.Right : AxisPlacement.Left; | ||||
|     } | ||||
| 
 | ||||
|     switch (props.placement) { | ||||
|       case AxisPlacement.Left: | ||||
|         this.hasLeftAxis = true; | ||||
|         break; | ||||
|       case AxisPlacement.Bottom: | ||||
|         this.hasBottomAxis = true; | ||||
|         break; | ||||
|     if (props.placement === AxisPlacement.Left) { | ||||
|       this.hasLeftAxis = true; | ||||
|     } | ||||
| 
 | ||||
|     if (props.placement === AxisPlacement.Hidden) { | ||||
|       props.show = false; | ||||
|       props.grid.show = false; | ||||
|       props.size = 0; | ||||
|     } | ||||
| 
 | ||||
|  | @ -233,24 +227,30 @@ export class UPlotConfigBuilder { | |||
|     return config; | ||||
|   } | ||||
| 
 | ||||
|   private ensureNonOverlappingAxes(axes: UPlotAxisBuilder[]): UPlotAxisBuilder[] { | ||||
|     for (const axis of axes) { | ||||
|       if (axis.props.placement === AxisPlacement.Right && this.hasLeftAxis) { | ||||
|         axis.props.grid = false; | ||||
|       } | ||||
|       if (axis.props.placement === AxisPlacement.Top && this.hasBottomAxis) { | ||||
|         axis.props.grid = false; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return axes; | ||||
|   } | ||||
| 
 | ||||
|   private tzDate = (ts: number) => { | ||||
|     let date = new Date(ts); | ||||
| 
 | ||||
|     return this.tz ? uPlot.tzDate(date, this.tz) : date; | ||||
|   }; | ||||
| 
 | ||||
|   private ensureNonOverlappingAxes(axes: UPlotAxisBuilder[]): UPlotAxisBuilder[] { | ||||
|     const xAxis = axes.find((a) => a.props.scaleKey === 'x'); | ||||
|     const axesWithoutGridSet = axes.filter((a) => a.props.grid?.show === undefined); | ||||
|     const firstValueAxisIdx = axesWithoutGridSet.findIndex( | ||||
|       (a) => a.props.placement === AxisPlacement.Left || (a.props.placement === AxisPlacement.Bottom && a !== xAxis) | ||||
|     ); | ||||
| 
 | ||||
|     // For all axes with no grid set, set the grid automatically (grid only for first left axis )
 | ||||
|     for (let i = 0; i < axesWithoutGridSet.length; i++) { | ||||
|       if (axesWithoutGridSet[i] === xAxis || i === firstValueAxisIdx) { | ||||
|         axesWithoutGridSet[i].props.grid!.show = true; | ||||
|       } else { | ||||
|         axesWithoutGridSet[i].props.grid!.show = false; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return axes; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** @alpha */ | ||||
|  |  | |||
|  | @ -17,11 +17,12 @@ export function addAxisConfig( | |||
|   defaultConfig: AxisConfig, | ||||
|   hideScale?: boolean | ||||
| ) { | ||||
|   const category = ['Axis']; | ||||
|   builder | ||||
|     .addRadio({ | ||||
|       path: 'axisPlacement', | ||||
|       name: 'Placement', | ||||
|       category: ['Axis'], | ||||
|       category, | ||||
|       defaultValue: graphFieldOptions.axisPlacement[0].value, | ||||
|       settings: { | ||||
|         options: graphFieldOptions.axisPlacement, | ||||
|  | @ -30,7 +31,7 @@ export function addAxisConfig( | |||
|     .addTextInput({ | ||||
|       path: 'axisLabel', | ||||
|       name: 'Label', | ||||
|       category: ['Axis'], | ||||
|       category, | ||||
|       defaultValue: '', | ||||
|       settings: { | ||||
|         placeholder: 'Optional text', | ||||
|  | @ -42,7 +43,7 @@ export function addAxisConfig( | |||
|     .addNumberInput({ | ||||
|       path: 'axisWidth', | ||||
|       name: 'Width', | ||||
|       category: ['Axis'], | ||||
|       category, | ||||
|       settings: { | ||||
|         placeholder: 'Auto', | ||||
|       }, | ||||
|  | @ -52,7 +53,7 @@ export function addAxisConfig( | |||
|       path: 'axisSoftMin', | ||||
|       name: 'Soft min', | ||||
|       defaultValue: defaultConfig.axisSoftMin, | ||||
|       category: ['Axis'], | ||||
|       category, | ||||
|       settings: { | ||||
|         placeholder: 'See: Standard options > Min', | ||||
|       }, | ||||
|  | @ -61,17 +62,31 @@ export function addAxisConfig( | |||
|       path: 'axisSoftMax', | ||||
|       name: 'Soft max', | ||||
|       defaultValue: defaultConfig.axisSoftMax, | ||||
|       category: ['Axis'], | ||||
|       category, | ||||
|       settings: { | ||||
|         placeholder: 'See: Standard options > Max', | ||||
|       }, | ||||
|     }) | ||||
|     .addRadio({ | ||||
|       path: 'axisGridShow', | ||||
|       name: 'Show grid lines', | ||||
|       category, | ||||
|       defaultValue: undefined, | ||||
|       settings: { | ||||
|         options: [ | ||||
|           { value: undefined, label: 'Auto' }, | ||||
|           { value: true, label: 'On' }, | ||||
|           { value: false, label: 'Off' }, | ||||
|         ], | ||||
|       }, | ||||
|     }); | ||||
| 
 | ||||
|   if (!hideScale) { | ||||
|     builder.addCustomEditor<void, ScaleDistributionConfig>({ | ||||
|       id: 'scaleDistribution', | ||||
|       path: 'scaleDistribution', | ||||
|       name: 'Scale', | ||||
|       category: ['Axis'], | ||||
|       category, | ||||
|       editor: ScaleDistributionEditor, | ||||
|       override: ScaleDistributionEditor, | ||||
|       defaultValue: { type: ScaleDistribution.Linear }, | ||||
|  |  | |||
|  | @ -99,7 +99,7 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<BarChartOptions> = ({ | |||
|     placement: vizOrientation.xOri === 0 ? AxisPlacement.Bottom : AxisPlacement.Left, | ||||
|     splits: config.xSplits, | ||||
|     values: config.xValues, | ||||
|     grid: false, | ||||
|     grid: { show: false }, | ||||
|     ticks: false, | ||||
|     gap: 15, | ||||
|     theme, | ||||
|  | @ -174,6 +174,7 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<BarChartOptions> = ({ | |||
|         placement, | ||||
|         formatValue: (v) => formattedValueToString(field.display!(v)), | ||||
|         theme, | ||||
|         grid: { show: customConfig.axisGridShow }, | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -167,6 +167,7 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<TimelineOptions> = ({ | |||
|     placement: AxisPlacement.Bottom, | ||||
|     timeZone, | ||||
|     theme, | ||||
|     grid: { show: true }, | ||||
|   }); | ||||
| 
 | ||||
|   builder.addAxis({ | ||||
|  | @ -175,7 +176,7 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<TimelineOptions> = ({ | |||
|     placement: AxisPlacement.Left, | ||||
|     splits: coreConfig.ySplits, | ||||
|     values: coreConfig.yValues, | ||||
|     grid: false, | ||||
|     grid: { show: false }, | ||||
|     ticks: false, | ||||
|     gap: 16, | ||||
|     theme, | ||||
|  |  | |||
|  | @ -40,6 +40,7 @@ export const TimeSeriesPanel: React.FC<TimeSeriesPanelProps> = ({ | |||
|   } | ||||
| 
 | ||||
|   const enableAnnotationCreation = Boolean(canAddAnnotations && canAddAnnotations()); | ||||
| 
 | ||||
|   return ( | ||||
|     <TimeSeries | ||||
|       frames={frames} | ||||
|  |  | |||
|  | @ -36,6 +36,7 @@ export const defaultGraphConfig: GraphFieldConfig = { | |||
|     mode: StackingMode.None, | ||||
|     group: 'A', | ||||
|   }, | ||||
|   axisGridShow: true, | ||||
| }; | ||||
| 
 | ||||
| const categoryStyles = ['Graph styles']; | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue