GraphNG: support auto and explicit axis width (#29553)

* auto axis

* auto axis

* expand everyhting with the same scale/unit

* expand everyhting with the same scale/unit
This commit is contained in:
Ryan McKinley 2020-12-03 00:30:40 -08:00 committed by GitHub
parent 60502463e2
commit 3c9310e93c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 75 additions and 20 deletions

View File

@ -114,14 +114,15 @@ export const GraphNG: React.FC<GraphNGProps> = ({
} }
const fmt = field.display ?? defaultFormatter; const fmt = field.display ?? defaultFormatter;
const scale = config.unit || '__fixed'; const scaleKey = config.unit || '__fixed';
const isNewScale = !builder.hasScale(scale);
if (isNewScale && customConfig.axisPlacement !== AxisPlacement.Hidden) { if (customConfig.axisPlacement !== AxisPlacement.Hidden) {
builder.addScale({ scaleKey: scale, min: field.config.min, max: field.config.max }); // The builder will manage unique scaleKeys and combine where appropriate
builder.addScale({ scaleKey, min: field.config.min, max: field.config.max });
builder.addAxis({ builder.addAxis({
scaleKey: scale, scaleKey,
label: customConfig.axisLabel, label: customConfig.axisLabel,
size: customConfig.axisWidth,
placement: customConfig.axisPlacement ?? AxisPlacement.Auto, placement: customConfig.axisPlacement ?? AxisPlacement.Auto,
formatValue: v => formattedValueToString(fmt(v)), formatValue: v => formattedValueToString(fmt(v)),
theme, theme,
@ -136,7 +137,7 @@ export const GraphNG: React.FC<GraphNGProps> = ({
const pointsMode = customConfig.mode === GraphMode.Points ? PointMode.Always : customConfig.points; const pointsMode = customConfig.mode === GraphMode.Points ? PointMode.Always : customConfig.points;
builder.addSeries({ builder.addSeries({
scaleKey: scale, scaleKey,
mode: customConfig.mode!, mode: customConfig.mode!,
lineColor: seriesColor, lineColor: seriesColor,
lineWidth: customConfig.lineWidth, lineWidth: customConfig.lineWidth,
@ -149,7 +150,7 @@ export const GraphNG: React.FC<GraphNGProps> = ({
}); });
if (hasLegend.current) { if (hasLegend.current) {
const axisPlacement = builder.getAxisPlacement(scale); const axisPlacement = builder.getAxisPlacement(scaleKey);
legendItems.push({ legendItems.push({
color: seriesColor, color: seriesColor,

View File

@ -3,13 +3,14 @@ import uPlot, { Axis } from 'uplot';
import { PlotConfigBuilder } from '../types'; import { PlotConfigBuilder } from '../types';
import { measureText } from '../../../utils/measureText'; import { measureText } from '../../../utils/measureText';
import { AxisPlacement } from '../config'; import { AxisPlacement } from '../config';
import { optMinMax } from './UPlotScaleBuilder';
export interface AxisProps { export interface AxisProps {
scaleKey: string; scaleKey: string;
theme: GrafanaTheme; theme: GrafanaTheme;
label?: string; label?: string;
show?: boolean; show?: boolean;
size?: number; size?: number | null;
placement?: AxisPlacement; placement?: AxisPlacement;
grid?: boolean; grid?: boolean;
formatValue?: (v: any) => string; formatValue?: (v: any) => string;
@ -19,6 +20,16 @@ export interface AxisProps {
} }
export class UPlotAxisBuilder extends PlotConfigBuilder<AxisProps, Axis> { export class UPlotAxisBuilder extends PlotConfigBuilder<AxisProps, Axis> {
merge(props: AxisProps) {
this.props.size = optMinMax('max', this.props.size, props.size);
if (!this.props.label) {
this.props.label = props.label;
}
if (this.props.placement === AxisPlacement.Auto) {
this.props.placement = props.placement;
}
}
getConfig(): Axis { getConfig(): Axis {
const { const {
scaleKey, scaleKey,
@ -42,7 +53,7 @@ export class UPlotAxisBuilder extends PlotConfigBuilder<AxisProps, Axis> {
side: getUPlotSideFromAxis(placement), side: getUPlotSideFromAxis(placement),
font: `12px 'Roboto'`, font: `12px 'Roboto'`,
labelFont: `12px 'Roboto'`, labelFont: `12px 'Roboto'`,
size: calculateAxisSize, size: this.props.size ?? calculateAxisSize,
grid: { grid: {
show: grid, show: grid,
stroke: gridColor, stroke: gridColor,
@ -107,8 +118,7 @@ function calculateAxisSize(self: uPlot, values: string[], axisIdx: number) {
} }
} }
let axisWidth = measureText(maxLength, 12).width + 18; return measureText(maxLength, 12).width + 18;
return axisWidth;
} }
/** Format time axis ticks */ /** Format time axis ticks */

View File

@ -61,7 +61,6 @@ describe('UPlotConfigBuilder', () => {
formatValue: () => 'test value', formatValue: () => 'test value',
grid: false, grid: false,
show: true, show: true,
size: 1,
theme: { isDark: true, palette: { gray25: '#ffffff' }, colors: { text: 'gray' } } as GrafanaTheme, theme: { isDark: true, palette: { gray25: '#ffffff' }, colors: { text: 'gray' } } as GrafanaTheme,
values: [], values: [],
}); });

View File

@ -8,13 +8,17 @@ export class UPlotConfigBuilder {
private series: UPlotSeriesBuilder[] = []; private series: UPlotSeriesBuilder[] = [];
private axes: Record<string, UPlotAxisBuilder> = {}; private axes: Record<string, UPlotAxisBuilder> = {};
private scales: UPlotScaleBuilder[] = []; private scales: UPlotScaleBuilder[] = [];
private registeredScales: string[] = [];
hasLeftAxis = false; hasLeftAxis = false;
addAxis(props: AxisProps) { addAxis(props: AxisProps) {
props.placement = props.placement ?? AxisPlacement.Auto; props.placement = props.placement ?? AxisPlacement.Auto;
if (this.axes[props.scaleKey]) {
this.axes[props.scaleKey].merge(props);
return;
}
// Handle auto placement logic // Handle auto placement logic
if (props.placement === AxisPlacement.Auto) { if (props.placement === AxisPlacement.Auto) {
props.placement = this.hasLeftAxis ? AxisPlacement.Right : AxisPlacement.Left; props.placement = this.hasLeftAxis ? AxisPlacement.Right : AxisPlacement.Left;
@ -36,15 +40,16 @@ export class UPlotConfigBuilder {
this.series.push(new UPlotSeriesBuilder(props)); this.series.push(new UPlotSeriesBuilder(props));
} }
/** Add or update the scale with the scale key */
addScale(props: ScaleProps) { addScale(props: ScaleProps) {
this.registeredScales.push(props.scaleKey); const current = this.scales.find(v => v.props.scaleKey === props.scaleKey);
if (current) {
current.merge(props);
return;
}
this.scales.push(new UPlotScaleBuilder(props)); this.scales.push(new UPlotScaleBuilder(props));
} }
hasScale(scaleKey: string) {
return this.registeredScales.indexOf(scaleKey) > -1;
}
getConfig() { getConfig() {
const config: PlotSeriesConfig = { series: [{}] }; const config: PlotSeriesConfig = { series: [{}] };
config.axes = Object.values(this.axes).map(a => a.getConfig()); config.axes = Object.values(this.axes).map(a => a.getConfig());

View File

@ -0,0 +1,21 @@
import { optMinMax } from './UPlotScaleBuilder';
describe('UPlotScaleBuilder', () => {
it('opt min max', () => {
expect(7).toEqual(optMinMax('min', null, 7));
expect(7).toEqual(optMinMax('min', undefined, 7));
expect(7).toEqual(optMinMax('min', 20, 7));
expect(7).toEqual(optMinMax('min', 7, null));
expect(7).toEqual(optMinMax('min', 7, undefined));
expect(7).toEqual(optMinMax('min', 7, 20));
expect(7).toEqual(optMinMax('max', null, 7));
expect(7).toEqual(optMinMax('max', undefined, 7));
expect(7).toEqual(optMinMax('max', 5, 7));
expect(7).toEqual(optMinMax('max', 7, null));
expect(7).toEqual(optMinMax('max', 7, undefined));
expect(7).toEqual(optMinMax('max', 7, 5));
});
});

View File

@ -9,6 +9,11 @@ export interface ScaleProps {
} }
export class UPlotScaleBuilder extends PlotConfigBuilder<ScaleProps, Scale> { export class UPlotScaleBuilder extends PlotConfigBuilder<ScaleProps, Scale> {
merge(props: ScaleProps) {
this.props.min = optMinMax('min', this.props.min, props.min);
this.props.max = optMinMax('max', this.props.max, props.max);
}
getConfig() { getConfig() {
const { isTime, scaleKey } = this.props; const { isTime, scaleKey } = this.props;
if (isTime) { if (isTime) {
@ -29,3 +34,18 @@ export class UPlotScaleBuilder extends PlotConfigBuilder<ScaleProps, Scale> {
}; };
} }
} }
export function optMinMax(minmax: 'min' | 'max', a?: number | null, b?: number | null): undefined | number | null {
const hasA = !(a === undefined || a === null);
const hasB = !(b === undefined || b === null);
if (hasA) {
if (!hasB) {
return a;
}
if (minmax === 'min') {
return a! < b! ? a : b;
}
return a! > b! ? a : b;
}
return b;
}

View File

@ -107,9 +107,8 @@ export const plugin = new PanelPlugin<Options, GraphFieldConfig>(GraphPanel)
path: 'axisWidth', path: 'axisWidth',
name: 'Width', name: 'Width',
category: ['Axis'], category: ['Axis'],
defaultValue: 60,
settings: { settings: {
placeholder: '60', placeholder: 'Auto',
}, },
showIf: c => c.axisPlacement !== AxisPlacement.Hidden, showIf: c => c.axisPlacement !== AxisPlacement.Hidden,
}); });