GraphNG: update the options config (#28917)

pull/29229/head
Ryan McKinley 5 years ago committed by GitHub
parent 006868339d
commit 1895626080
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      packages/grafana-ui/src/components/GraphNG/GraphNG.test.tsx
  2. 46
      packages/grafana-ui/src/components/GraphNG/GraphNG.tsx
  3. 2
      packages/grafana-ui/src/components/index.ts
  4. 85
      packages/grafana-ui/src/components/uPlot/config.ts
  5. 50
      packages/grafana-ui/src/components/uPlot/types.ts
  6. 126
      public/app/plugins/panel/graph3/module.tsx

@ -2,7 +2,7 @@ import React from 'react';
import { GraphNG } from './GraphNG';
import { render } from '@testing-library/react';
import { ArrayVector, dateTime, FieldConfig, FieldType, MutableDataFrame } from '@grafana/data';
import { GraphCustomFieldConfig } from '..';
import { GraphFieldConfig, GraphMode } from '../uPlot/config';
const mockData = () => {
const data = new MutableDataFrame();
@ -20,9 +20,9 @@ const mockData = () => {
values: new ArrayVector([10, 20, 5]),
config: {
custom: {
line: { show: true },
mode: GraphMode.Line,
},
} as FieldConfig<GraphCustomFieldConfig>,
} as FieldConfig<GraphFieldConfig>,
});
const timeRange = {

@ -11,7 +11,8 @@ import {
} from '@grafana/data';
import { alignAndSortDataFramesByFieldName } from './utils';
import { UPlotChart } from '../uPlot/Plot';
import { AxisSide, GraphCustomFieldConfig, PlotProps } from '../uPlot/types';
import { PlotProps } from '../uPlot/types';
import { AxisPlacement, getUPlotSideFromAxis, GraphFieldConfig, GraphMode, PointMode } from '../uPlot/config';
import { useTheme } from '../../themes';
import { VizLayout } from '../VizLayout/VizLayout';
import { LegendDisplayMode, LegendItem, LegendOptions } from '../Legend/Legend';
@ -25,6 +26,12 @@ interface GraphNGProps extends Omit<PlotProps, 'data' | 'config'> {
legend?: LegendOptions;
}
const defaultConfig: GraphFieldConfig = {
mode: GraphMode.Line,
points: PointMode.Auto,
axisPlacement: AxisPlacement.Auto,
};
export const GraphNG: React.FC<GraphNGProps> = ({
data,
children,
@ -68,18 +75,20 @@ export const GraphNG: React.FC<GraphNGProps> = ({
builder.addAxis({
scaleKey: 'x',
isTime: true,
side: AxisSide.Bottom,
side: getUPlotSideFromAxis(AxisPlacement.Bottom),
timeZone,
theme,
});
let seriesIdx = 0;
const legendItems: LegendItem[] = [];
let hasLeftAxis = false;
let hasYAxis = false;
for (let i = 0; i < alignedData.fields.length; i++) {
const field = alignedData.fields[i];
const config = field.config as FieldConfig<GraphCustomFieldConfig>;
const customConfig = config.custom;
const config = field.config as FieldConfig<GraphFieldConfig>;
const customConfig = config.custom || defaultConfig;
if (i === timeIndex || field.type !== FieldType.number) {
continue;
@ -87,18 +96,23 @@ export const GraphNG: React.FC<GraphNGProps> = ({
const fmt = field.display ?? defaultFormatter;
const scale = config.unit || '__fixed';
const side = customConfig.axisPlacement ?? (hasLeftAxis ? AxisPlacement.Right : AxisPlacement.Left);
if (!builder.hasScale(scale) && customConfig.axisPlacement !== AxisPlacement.Hidden) {
if (side === AxisPlacement.Left) {
hasLeftAxis = true;
}
if (!builder.hasScale(scale)) {
builder.addScale({ scaleKey: scale });
builder.addAxis({
scaleKey: scale,
label: config.custom?.axis?.label,
size: config.custom?.axis?.width,
side: config.custom?.axis?.side || AxisSide.Left,
grid: config.custom?.axis?.grid,
label: customConfig.axisLabel,
side: getUPlotSideFromAxis(side),
grid: !hasYAxis,
formatValue: v => formattedValueToString(fmt(v)),
theme,
});
hasYAxis = true;
}
// need to update field state here because we use a transform to merge framesP
@ -109,14 +123,14 @@ export const GraphNG: React.FC<GraphNGProps> = ({
builder.addSeries({
scaleKey: scale,
line: customConfig?.line?.show,
line: (customConfig.mode ?? GraphMode.Line) === GraphMode.Line,
lineColor: seriesColor,
lineWidth: customConfig?.line?.width,
points: customConfig?.points?.show,
pointSize: customConfig?.points?.radius,
lineWidth: customConfig.lineWidth,
points: customConfig.points !== PointMode.Never,
pointSize: customConfig.pointRadius,
pointColor: seriesColor,
fill: customConfig?.fill?.alpha !== undefined,
fillOpacity: customConfig?.fill?.alpha,
fill: customConfig.fillAlpha !== undefined,
fillOpacity: customConfig.fillAlpha,
fillColor: seriesColor,
});
@ -124,7 +138,7 @@ export const GraphNG: React.FC<GraphNGProps> = ({
legendItems.push({
color: seriesColor,
label: getFieldDisplayName(field, alignedData),
yAxis: customConfig?.axis?.side === 1 ? 3 : 1,
yAxis: side === AxisPlacement.Right ? 3 : 1,
});
}

@ -207,7 +207,7 @@ const LegacyForms = {
export { LegacyForms, LegacyInputStatus };
// WIP, need renames and exports cleanup
export { GraphCustomFieldConfig, AxisSide } from './uPlot/types';
export { GraphFieldConfig, graphFieldOptions } from './uPlot/config';
export { UPlotChart } from './uPlot/Plot';
export * from './uPlot/geometries';
export * from './uPlot/plugins';

@ -0,0 +1,85 @@
import { SelectableValue } from '@grafana/data';
export enum AxisPlacement {
Auto = 'auto', // First axis on the left, the rest on the right
Top = 'top',
Right = 'right',
Bottom = 'bottom',
Left = 'left',
Hidden = 'hidden',
}
export function getUPlotSideFromAxis(axis: AxisPlacement) {
switch (axis) {
case AxisPlacement.Top:
return 0;
case AxisPlacement.Right:
return 1;
case AxisPlacement.Bottom:
return 2;
case AxisPlacement.Left:
}
return 3; // default everythign to the left
}
export enum PointMode {
Auto = 'auto', // will show points when the density is low or line is hidden
Always = 'always',
Never = 'never',
}
export enum GraphMode {
Line = 'line', // default
Bar = 'bar', // will also have a gap percent
Points = 'points', // Only show points
}
export enum LineInterpolation {
Linear = 'linear',
Staircase = 'staircase', // https://leeoniya.github.io/uPlot/demos/line-stepped.html
Smooth = 'smooth', // https://leeoniya.github.io/uPlot/demos/line-smoothing.html
}
export interface GraphFieldConfig {
mode: GraphMode;
lineMode?: LineInterpolation;
lineWidth?: number; // pixels
fillAlpha?: number; // 0-1
points?: PointMode;
pointRadius?: number; // pixels
symbol?: string; // eventually dot,star, etc
// Axis is actually unique based on the unit... not each field!
axisPlacement?: AxisPlacement;
axisLabel?: string;
axisWidth?: number; // pixels ideally auto?
}
export const graphFieldOptions = {
mode: [
{ label: 'Lines', value: GraphMode.Line },
{ label: 'Bars', value: GraphMode.Bar },
{ label: 'Points', value: GraphMode.Points },
] as Array<SelectableValue<GraphMode>>,
lineMode: [
{ label: 'Linear', value: LineInterpolation.Linear },
{ label: 'Staircase', value: LineInterpolation.Staircase },
{ label: 'Smooth', value: LineInterpolation.Smooth },
] as Array<SelectableValue<LineInterpolation>>,
points: [
{ label: 'Auto', value: PointMode.Auto, description: 'Show points when the density is low' },
{ label: 'Always', value: PointMode.Always },
{ label: 'Never', value: PointMode.Never },
] as Array<SelectableValue<PointMode>>,
axisPlacement: [
{ label: 'Auto', value: AxisPlacement.Auto, description: 'First field on the left, everything else on the right' },
{ label: 'Left', value: AxisPlacement.Left },
{ label: 'Right', value: AxisPlacement.Right },
{ label: 'Hidden', value: AxisPlacement.Hidden },
] as Array<SelectableValue<AxisPlacement>>,
};

@ -1,49 +1,8 @@
import React from 'react';
import uPlot from 'uplot';
import { DataFrame, FieldColor, TimeRange, TimeZone } from '@grafana/data';
import { DataFrame, TimeRange, TimeZone } from '@grafana/data';
import { UPlotConfigBuilder } from './config/UPlotConfigBuilder';
export type NullValuesMode = 'null' | 'connected' | 'asZero';
export enum AxisSide {
Top,
Right,
Bottom,
Left,
}
interface AxisConfig {
label: string;
side: AxisSide;
grid: boolean;
width: number;
}
interface LineConfig {
show: boolean;
width: number;
color: FieldColor;
}
interface PointConfig {
show: boolean;
radius: number;
}
interface BarsConfig {
show: boolean;
}
interface FillConfig {
alpha: number;
}
export interface GraphCustomFieldConfig {
axis: AxisConfig;
line: LineConfig;
points: PointConfig;
bars: BarsConfig;
fill: FillConfig;
nullValues: NullValuesMode;
}
export type PlotSeriesConfig = Pick<uPlot.Options, 'series' | 'scales' | 'axes'>;
export type PlotPlugin = {
id: string;
@ -74,3 +33,10 @@ export abstract class PlotConfigBuilder<P, T> {
constructor(protected props: P) {}
abstract getConfig(): T;
}
export enum AxisSide {
Top, // 0
Right, // 1
Bottom, // 2
Left, // 3
}

@ -1,9 +1,16 @@
import { FieldColorModeId, FieldConfigProperty, PanelPlugin } from '@grafana/data';
import { AxisSide, GraphCustomFieldConfig, LegendDisplayMode } from '@grafana/ui';
import { LegendDisplayMode } from '@grafana/ui';
import {
GraphFieldConfig,
PointMode,
GraphMode,
AxisPlacement,
graphFieldOptions,
} from '@grafana/ui/src/components/uPlot/config';
import { GraphPanel } from './GraphPanel';
import { Options } from './types';
export const plugin = new PanelPlugin<Options, GraphCustomFieldConfig>(GraphPanel)
export const plugin = new PanelPlugin<Options, GraphFieldConfig>(GraphPanel)
.useFieldConfig({
standardOptions: {
[FieldConfigProperty.Color]: {
@ -17,14 +24,26 @@ export const plugin = new PanelPlugin<Options, GraphCustomFieldConfig>(GraphPane
},
useCustomConfig: builder => {
builder
.addBooleanSwitch({
path: 'line.show',
name: 'Show lines',
description: '',
defaultValue: true,
.addRadio({
path: 'mode',
name: 'Display',
defaultValue: graphFieldOptions.mode[0].value,
settings: {
options: graphFieldOptions.mode,
},
})
.addRadio({
path: 'lineMode',
name: 'Line interpolation',
description: 'NOTE: not implemented yet',
defaultValue: graphFieldOptions.lineMode[0].value,
settings: {
options: graphFieldOptions.lineMode,
},
showIf: c => !(c.mode === GraphMode.Bar || c.mode === GraphMode.Points),
})
.addSliderInput({
path: 'line.width',
path: 'lineWidth',
name: 'Line width',
defaultValue: 1,
settings: {
@ -32,18 +51,30 @@ export const plugin = new PanelPlugin<Options, GraphCustomFieldConfig>(GraphPane
max: 10,
step: 1,
},
showIf: c => {
return c.line.show;
showIf: c => !(c.mode === GraphMode.Bar || c.mode === GraphMode.Points),
})
.addSliderInput({
path: 'fillAlpha',
name: 'Fill area opacity',
defaultValue: 0.1,
settings: {
min: 0,
max: 1,
step: 0.1,
},
showIf: c => !(c.mode === GraphMode.Bar || c.mode === GraphMode.Points),
})
.addBooleanSwitch({
path: 'points.show',
name: 'Show points',
description: '',
defaultValue: false,
.addRadio({
path: 'points',
name: 'Points',
description: 'NOTE: auto vs always are currently the same',
defaultValue: graphFieldOptions.points[0].value,
settings: {
options: graphFieldOptions.points,
},
})
.addSliderInput({
path: 'points.radius',
path: 'pointRadius',
name: 'Point radius',
defaultValue: 4,
settings: {
@ -51,75 +82,38 @@ export const plugin = new PanelPlugin<Options, GraphCustomFieldConfig>(GraphPane
max: 10,
step: 1,
},
showIf: c => c.points.show,
})
.addBooleanSwitch({
path: 'bars.show',
name: 'Show bars',
description: '',
defaultValue: false,
showIf: c => c.points !== PointMode.Never,
})
.addSliderInput({
path: 'fill.alpha',
name: 'Fill area opacity',
defaultValue: 0,
.addRadio({
path: 'axisPlacement',
name: 'Placement',
category: ['Axis'],
defaultValue: graphFieldOptions.axisPlacement[0].value,
settings: {
min: 0,
max: 1,
step: 0.1,
options: graphFieldOptions.axisPlacement,
},
})
.addTextInput({
path: 'axis.label',
name: 'Axis Label',
path: 'axisLabel',
name: 'Label',
category: ['Axis'],
defaultValue: '',
settings: {
placeholder: 'Optional text',
},
showIf: c => c.axisPlacement !== AxisPlacement.Hidden,
// no matter what the field type is
shouldApply: () => true,
})
.addRadio({
path: 'axis.side',
name: 'Y axis side',
category: ['Axis'],
defaultValue: AxisSide.Left,
settings: {
options: [
{ value: AxisSide.Left, label: 'Left' },
{ value: AxisSide.Right, label: 'Right' },
],
},
})
.addNumberInput({
path: 'axis.width',
name: 'Y axis width',
path: 'axisWidth',
name: 'Width',
category: ['Axis'],
defaultValue: 60,
settings: {
placeholder: '60',
},
})
.addBooleanSwitch({
path: 'axis.grid',
name: 'Show axis grid',
category: ['Axis'],
description: '',
defaultValue: true,
})
.addRadio({
path: 'nullValues',
name: 'Display null values as',
description: '',
defaultValue: 'null',
settings: {
options: [
{ value: 'null', label: 'null' },
{ value: 'connected', label: 'Connected' },
{ value: 'asZero', label: 'Zero' },
],
},
showIf: c => c.axisPlacement !== AxisPlacement.Hidden,
});
},
})

Loading…
Cancel
Save