mirror of
https://github.com/grafana/grafana.git
synced 2024-11-26 02:40:26 -06:00
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:
|
||||
if (props.placement === AxisPlacement.Left) {
|
||||
this.hasLeftAxis = true;
|
||||
break;
|
||||
case AxisPlacement.Bottom:
|
||||
this.hasBottomAxis = true;
|
||||
break;
|
||||
}
|
||||
|
||||
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
Block a user