Commit 71e93e52 by Ryan McKinley Committed by GitHub

GraphNG: support dashes (#30070)

parent dba4942e
...@@ -175,6 +175,7 @@ export const GraphNG: React.FC<GraphNGProps> = ({ ...@@ -175,6 +175,7 @@ export const GraphNG: React.FC<GraphNGProps> = ({
lineColor: customConfig.lineColor ?? seriesColor, lineColor: customConfig.lineColor ?? seriesColor,
lineWidth: customConfig.lineWidth, lineWidth: customConfig.lineWidth,
lineInterpolation: customConfig.lineInterpolation, lineInterpolation: customConfig.lineInterpolation,
lineStyle: customConfig.lineStyle,
showPoints, showPoints,
pointSize: customConfig.pointSize, pointSize: customConfig.pointSize,
pointColor: customConfig.pointColor ?? seriesColor, pointColor: customConfig.pointColor ?? seriesColor,
......
...@@ -51,11 +51,19 @@ export enum ScaleDistribution { ...@@ -51,11 +51,19 @@ export enum ScaleDistribution {
/** /**
* @alpha * @alpha
*/ */
export interface LineStyle {
fill?: 'solid' | 'dash' | 'dot' | 'square'; // cap = 'butt' | 'round' | 'square'
dash?: number[];
}
/**
* @alpha
*/
export interface LineConfig { export interface LineConfig {
lineColor?: string; lineColor?: string;
lineWidth?: number; lineWidth?: number;
lineInterpolation?: LineInterpolation; lineInterpolation?: LineInterpolation;
lineDash?: number[]; lineStyle?: LineStyle;
spanNulls?: boolean; spanNulls?: boolean;
} }
......
...@@ -25,6 +25,7 @@ export class UPlotSeriesBuilder extends PlotConfigBuilder<SeriesProps, Series> { ...@@ -25,6 +25,7 @@ export class UPlotSeriesBuilder extends PlotConfigBuilder<SeriesProps, Series> {
lineInterpolation, lineInterpolation,
lineColor, lineColor,
lineWidth, lineWidth,
lineStyle,
showPoints, showPoints,
pointColor, pointColor,
pointSize, pointSize,
...@@ -40,6 +41,12 @@ export class UPlotSeriesBuilder extends PlotConfigBuilder<SeriesProps, Series> { ...@@ -40,6 +41,12 @@ export class UPlotSeriesBuilder extends PlotConfigBuilder<SeriesProps, Series> {
} else { } else {
lineConfig.stroke = lineColor; lineConfig.stroke = lineColor;
lineConfig.width = lineWidth; lineConfig.width = lineWidth;
if (lineStyle && lineStyle.fill !== 'solid') {
if (lineStyle.fill === 'dot') {
// lineConfig.dashCap = 'round'; // square or butt
}
lineConfig.dash = lineStyle.dash ?? [10, 10];
}
lineConfig.paths = (self: uPlot, seriesIdx: number, idx0: number, idx1: number) => { lineConfig.paths = (self: uPlot, seriesIdx: number, idx0: number, idx1: number) => {
let pathsBuilder = mapDrawStyleToPathBuilder(drawStyle, lineInterpolation); let pathsBuilder = mapDrawStyleToPathBuilder(drawStyle, lineInterpolation);
return pathsBuilder(self, seriesIdx, idx0, idx1); return pathsBuilder(self, seriesIdx, idx0, idx1);
......
import React, { useMemo } from 'react';
import { FieldOverrideEditorProps, SelectableValue } from '@grafana/data';
import { HorizontalGroup, IconButton, LineStyle, RadioButtonGroup, Select } from '@grafana/ui';
type LineFill = 'solid' | 'dash' | 'dot';
const lineFillOptions: Array<SelectableValue<LineFill>> = [
{
label: 'Solid',
value: 'solid',
},
{
label: 'Dash',
value: 'dash',
},
// {
// label: 'Dots',
// value: 'dot',
// },
];
const dashOptions: Array<SelectableValue<string>> = [
'10, 10', // default
'10, 15',
'10, 20',
'10, 25',
'10, 30',
'10, 40',
'15, 10',
'20, 10',
'25, 10',
'30, 10',
'40, 10',
'50, 10',
'5, 10',
'30, 3, 3',
].map(txt => ({
label: txt,
value: txt,
}));
const dotOptions: Array<SelectableValue<string>> = [
'0, 10', // default
'0, 20',
'0, 30',
'0, 40',
'0, 3, 3',
].map(txt => ({
label: txt,
value: txt,
}));
export const LineStyleEditor: React.FC<FieldOverrideEditorProps<LineStyle, any>> = ({ value, onChange }) => {
const options = useMemo(() => (value?.fill === 'dash' ? dashOptions : dotOptions), [value]);
const current = useMemo(() => {
if (!value?.dash?.length) {
return options[0];
}
const str = value.dash?.join(', ');
const val = options.find(o => o.value === str);
if (!val) {
return {
label: str,
value: str,
};
}
return val;
}, [value, options]);
return (
<HorizontalGroup>
<RadioButtonGroup
value={value?.fill || 'solid'}
options={lineFillOptions}
onChange={v => {
onChange({
...value,
fill: v!,
});
}}
/>
{value?.fill && value?.fill !== 'solid' && (
<>
<Select
allowCustomValue={true}
options={options}
value={current}
width={20}
onChange={v => {
onChange({
...value,
dash: parseText(v.value ?? ''),
});
}}
formatCreateLabel={t => `Segments: ${parseText(t).join(', ')}`}
/>
<div>
&nbsp;
<a
title="The input expects a segment list"
href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash#Parameters"
target="_blank"
rel="noreferrer"
>
<IconButton name="question-circle" />
</a>
</div>
</>
)}
</HorizontalGroup>
);
};
function parseText(txt: string): number[] {
const segments: number[] = [];
for (const s of txt.split(/(?:,| )+/)) {
const num = Number.parseInt(s, 10);
if (!isNaN(num)) {
segments.push(num);
}
}
return segments;
}
...@@ -117,6 +117,13 @@ Object { ...@@ -117,6 +117,13 @@ Object {
"axisPlacement": "auto", "axisPlacement": "auto",
"drawStyle": "line", "drawStyle": "line",
"fillOpacity": 10, "fillOpacity": 10,
"lineStyle": Object {
"dash": Array [
10,
10,
],
"fill": "dash",
},
"lineWidth": 1, "lineWidth": 1,
"pointSize": 6, "pointSize": 6,
"showPoints": "never", "showPoints": "never",
...@@ -155,6 +162,16 @@ Object { ...@@ -155,6 +162,16 @@ Object {
"id": "custom.axisLabel", "id": "custom.axisLabel",
"value": "Y222", "value": "Y222",
}, },
Object {
"id": "custom.lineStyle",
"value": Object {
"dash": Array [
5,
8,
],
"fill": "dash",
},
},
], ],
}, },
], ],
......
...@@ -186,6 +186,9 @@ const twoYAxis = { ...@@ -186,6 +186,9 @@ const twoYAxis = {
{ {
alias: 'B-series', alias: 'B-series',
yaxis: 2, yaxis: 2,
dashLength: 5,
dashes: true,
spaceLength: 8,
}, },
], ],
thresholds: [], thresholds: [],
...@@ -199,7 +202,7 @@ const twoYAxis = { ...@@ -199,7 +202,7 @@ const twoYAxis = {
}, },
], ],
fillGradient: 0, fillGradient: 0,
dashes: false, dashes: true,
hiddenSeries: false, hiddenSeries: false,
points: false, points: false,
bars: false, bars: false,
......
...@@ -16,6 +16,7 @@ import { ...@@ -16,6 +16,7 @@ import {
AxisPlacement, AxisPlacement,
DrawStyle, DrawStyle,
LineInterpolation, LineInterpolation,
LineStyle,
PointVisibility, PointVisibility,
} from '@grafana/ui/src/components/uPlot/config'; } from '@grafana/ui/src/components/uPlot/config';
import { Options } from './types'; import { Options } from './types';
...@@ -52,6 +53,12 @@ export function flotToGraphOptions(angular: any): { fieldConfig: FieldConfigSour ...@@ -52,6 +53,12 @@ export function flotToGraphOptions(angular: any): { fieldConfig: FieldConfigSour
}; };
} }
// Dashes
const dash: LineStyle = {
fill: angular.dashes ? 'dash' : 'solid',
dash: [angular.dashLength ?? 10, angular.spaceLength ?? 10],
};
// "seriesOverrides": [ // "seriesOverrides": [
// { // {
// "$$hashKey": "object:183", // "$$hashKey": "object:183",
...@@ -98,6 +105,8 @@ export function flotToGraphOptions(angular: any): { fieldConfig: FieldConfigSour ...@@ -98,6 +105,8 @@ export function flotToGraphOptions(angular: any): { fieldConfig: FieldConfigSour
}, },
properties: [], properties: [],
}; };
let dashOverride: LineStyle | undefined = undefined;
for (const p of Object.keys(seriesOverride)) { for (const p of Object.keys(seriesOverride)) {
const v = seriesOverride[p]; const v = seriesOverride[p];
switch (p) { switch (p) {
...@@ -165,10 +174,37 @@ export function flotToGraphOptions(angular: any): { fieldConfig: FieldConfigSour ...@@ -165,10 +174,37 @@ export function flotToGraphOptions(angular: any): { fieldConfig: FieldConfigSour
value: 2 + v * 2, value: 2 + v * 2,
}); });
break; break;
case 'dashLength':
case 'spaceLength':
case 'dashes':
if (!dashOverride) {
dashOverride = {
fill: dash.fill,
dash: [...dash.dash!],
};
}
switch (p) {
case 'dashLength':
dashOverride.dash![0] = v;
break;
case 'spaceLength':
dashOverride.dash![1] = v;
break;
case 'dashes':
dashOverride.fill = v ? 'dash' : 'solid';
break;
}
break;
default: default:
console.log('Ignore override migration:', seriesOverride.alias, p, v); console.log('Ignore override migration:', seriesOverride.alias, p, v);
} }
} }
if (dashOverride) {
rule.properties.push({
id: 'custom.lineStyle',
value: dashOverride,
});
}
if (rule.properties.length) { if (rule.properties.length) {
overrides.push(rule); overrides.push(rule);
} }
...@@ -185,6 +221,9 @@ export function flotToGraphOptions(angular: any): { fieldConfig: FieldConfigSour ...@@ -185,6 +221,9 @@ export function flotToGraphOptions(angular: any): { fieldConfig: FieldConfigSour
} }
graph.lineWidth = angular.linewidth; graph.lineWidth = angular.linewidth;
if (dash.fill !== 'solid') {
graph.lineStyle = dash;
}
if (isNumber(angular.pointradius)) { if (isNumber(angular.pointradius)) {
graph.pointSize = 2 + angular.pointradius * 2; graph.pointSize = 2 + angular.pointradius * 2;
......
...@@ -11,6 +11,7 @@ import { ...@@ -11,6 +11,7 @@ import {
GraphFieldConfig, GraphFieldConfig,
graphFieldOptions, graphFieldOptions,
LegendDisplayMode, LegendDisplayMode,
LineStyle,
PointVisibility, PointVisibility,
ScaleDistribution, ScaleDistribution,
ScaleDistributionConfig, ScaleDistributionConfig,
...@@ -20,6 +21,7 @@ import { GraphPanel } from './GraphPanel'; ...@@ -20,6 +21,7 @@ import { GraphPanel } from './GraphPanel';
import { graphPanelChangedHandler } from './migrations'; import { graphPanelChangedHandler } from './migrations';
import { Options } from './types'; import { Options } from './types';
import { ScaleDistributionEditor } from './ScaleDistributionEditor'; import { ScaleDistributionEditor } from './ScaleDistributionEditor';
import { LineStyleEditor } from './LineStyleEditor';
export const plugin = new PanelPlugin<Options, GraphFieldConfig>(GraphPanel) export const plugin = new PanelPlugin<Options, GraphFieldConfig>(GraphPanel)
.setPanelChangeHandler(graphPanelChangedHandler) .setPanelChangeHandler(graphPanelChangedHandler)
...@@ -75,6 +77,16 @@ export const plugin = new PanelPlugin<Options, GraphFieldConfig>(GraphPanel) ...@@ -75,6 +77,16 @@ export const plugin = new PanelPlugin<Options, GraphFieldConfig>(GraphPanel)
}, },
showIf: c => c.drawStyle !== DrawStyle.Points, showIf: c => c.drawStyle !== DrawStyle.Points,
}) })
.addCustomEditor<void, LineStyle>({
id: 'lineStyle',
path: 'lineStyle',
name: 'Line style',
showIf: c => c.drawStyle === DrawStyle.Line,
editor: LineStyleEditor,
override: LineStyleEditor,
process: identityOverrideProcessor,
shouldApply: f => f.type === FieldType.number,
})
.addRadio({ .addRadio({
path: 'fillGradient', path: 'fillGradient',
name: 'Fill gradient', name: 'Fill gradient',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment