Commit 71e93e52 by Ryan McKinley Committed by GitHub

GraphNG: support dashes (#30070)

parent dba4942e
......@@ -175,6 +175,7 @@ export const GraphNG: React.FC<GraphNGProps> = ({
lineColor: customConfig.lineColor ?? seriesColor,
lineWidth: customConfig.lineWidth,
lineInterpolation: customConfig.lineInterpolation,
lineStyle: customConfig.lineStyle,
showPoints,
pointSize: customConfig.pointSize,
pointColor: customConfig.pointColor ?? seriesColor,
......
......@@ -51,11 +51,19 @@ export enum ScaleDistribution {
/**
* @alpha
*/
export interface LineStyle {
fill?: 'solid' | 'dash' | 'dot' | 'square'; // cap = 'butt' | 'round' | 'square'
dash?: number[];
}
/**
* @alpha
*/
export interface LineConfig {
lineColor?: string;
lineWidth?: number;
lineInterpolation?: LineInterpolation;
lineDash?: number[];
lineStyle?: LineStyle;
spanNulls?: boolean;
}
......
......@@ -25,6 +25,7 @@ export class UPlotSeriesBuilder extends PlotConfigBuilder<SeriesProps, Series> {
lineInterpolation,
lineColor,
lineWidth,
lineStyle,
showPoints,
pointColor,
pointSize,
......@@ -40,6 +41,12 @@ export class UPlotSeriesBuilder extends PlotConfigBuilder<SeriesProps, Series> {
} else {
lineConfig.stroke = lineColor;
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) => {
let pathsBuilder = mapDrawStyleToPathBuilder(drawStyle, lineInterpolation);
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 {
"axisPlacement": "auto",
"drawStyle": "line",
"fillOpacity": 10,
"lineStyle": Object {
"dash": Array [
10,
10,
],
"fill": "dash",
},
"lineWidth": 1,
"pointSize": 6,
"showPoints": "never",
......@@ -155,6 +162,16 @@ Object {
"id": "custom.axisLabel",
"value": "Y222",
},
Object {
"id": "custom.lineStyle",
"value": Object {
"dash": Array [
5,
8,
],
"fill": "dash",
},
},
],
},
],
......
......@@ -186,6 +186,9 @@ const twoYAxis = {
{
alias: 'B-series',
yaxis: 2,
dashLength: 5,
dashes: true,
spaceLength: 8,
},
],
thresholds: [],
......@@ -199,7 +202,7 @@ const twoYAxis = {
},
],
fillGradient: 0,
dashes: false,
dashes: true,
hiddenSeries: false,
points: false,
bars: false,
......
......@@ -16,6 +16,7 @@ import {
AxisPlacement,
DrawStyle,
LineInterpolation,
LineStyle,
PointVisibility,
} from '@grafana/ui/src/components/uPlot/config';
import { Options } from './types';
......@@ -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": [
// {
// "$$hashKey": "object:183",
......@@ -98,6 +105,8 @@ export function flotToGraphOptions(angular: any): { fieldConfig: FieldConfigSour
},
properties: [],
};
let dashOverride: LineStyle | undefined = undefined;
for (const p of Object.keys(seriesOverride)) {
const v = seriesOverride[p];
switch (p) {
......@@ -165,10 +174,37 @@ export function flotToGraphOptions(angular: any): { fieldConfig: FieldConfigSour
value: 2 + v * 2,
});
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:
console.log('Ignore override migration:', seriesOverride.alias, p, v);
}
}
if (dashOverride) {
rule.properties.push({
id: 'custom.lineStyle',
value: dashOverride,
});
}
if (rule.properties.length) {
overrides.push(rule);
}
......@@ -185,6 +221,9 @@ export function flotToGraphOptions(angular: any): { fieldConfig: FieldConfigSour
}
graph.lineWidth = angular.linewidth;
if (dash.fill !== 'solid') {
graph.lineStyle = dash;
}
if (isNumber(angular.pointradius)) {
graph.pointSize = 2 + angular.pointradius * 2;
......
......@@ -11,6 +11,7 @@ import {
GraphFieldConfig,
graphFieldOptions,
LegendDisplayMode,
LineStyle,
PointVisibility,
ScaleDistribution,
ScaleDistributionConfig,
......@@ -20,6 +21,7 @@ import { GraphPanel } from './GraphPanel';
import { graphPanelChangedHandler } from './migrations';
import { Options } from './types';
import { ScaleDistributionEditor } from './ScaleDistributionEditor';
import { LineStyleEditor } from './LineStyleEditor';
export const plugin = new PanelPlugin<Options, GraphFieldConfig>(GraphPanel)
.setPanelChangeHandler(graphPanelChangedHandler)
......@@ -75,6 +77,16 @@ export const plugin = new PanelPlugin<Options, GraphFieldConfig>(GraphPanel)
},
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({
path: 'fillGradient',
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