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:
Dominik Prokop 2021-08-27 19:30:42 +02:00 committed by GitHub
parent b5e4a0a39a
commit 1be53b4f3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 159 additions and 38 deletions

View File

@ -144,6 +144,7 @@ export interface AxisConfig {
axisWidth?: number; // pixels ideally auto?
axisSoftMin?: number;
axisSoftMax?: number;
axisGridShow?: boolean;
scaleDistribution?: ScaleDistributionConfig;
}

View File

@ -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';

View File

@ -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) => {

View File

@ -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 },
});
}

View File

@ -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,
},

View File

@ -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);
});
});
});

View File

@ -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 */

View File

@ -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 },

View File

@ -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 },
});
}

View File

@ -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,

View File

@ -40,6 +40,7 @@ export const TimeSeriesPanel: React.FC<TimeSeriesPanelProps> = ({
}
const enableAnnotationCreation = Boolean(canAddAnnotations && canAddAnnotations());
return (
<TimeSeries
frames={frames}

View File

@ -36,6 +36,7 @@ export const defaultGraphConfig: GraphFieldConfig = {
mode: StackingMode.None,
group: 'A',
},
axisGridShow: true,
};
const categoryStyles = ['Graph styles'];