mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
GraphNG: support dashes (#30070)
This commit is contained in:
parent
dba4942edf
commit
71e93e521b
File diff suppressed because it is too large
Load Diff
@ -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,
|
||||
|
@ -48,6 +48,14 @@ export enum ScaleDistribution {
|
||||
Logarithmic = 'log',
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface LineStyle {
|
||||
fill?: 'solid' | 'dash' | 'dot' | 'square'; // cap = 'butt' | 'round' | 'square'
|
||||
dash?: number[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
@ -55,7 +63,7 @@ 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);
|
||||
|
123
public/app/plugins/panel/graph3/LineStyleEditor.tsx
Normal file
123
public/app/plugins/panel/graph3/LineStyleEditor.tsx
Normal file
@ -0,0 +1,123 @@
|
||||
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>
|
||||
|
||||
<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',
|
||||
|
Loading…
Reference in New Issue
Block a user