From 382c75d0db0bffcb5405a3603e18f947a6f7129d Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Thu, 14 Jan 2021 13:54:21 +0100 Subject: [PATCH] GraphNG: added support to change series color from legend. (#30256) * added support for changing color of series. * removed dependency on internal type. --- .../src/components/GraphNG/GraphNG.tsx | 3 + .../panel/timeseries/TimeSeriesPanel.tsx | 11 +- .../colorSeriesConfigFactory.test.ts | 151 ++++++++++++++++++ .../overrides/colorSeriesConfigFactory.ts | 74 +++++++++ .../hideSeriesConfigFactory.test.ts | 0 .../hideSeriesConfigFactory.ts | 0 .../plugins/panel/xychart/XYChartPanel.tsx | 11 +- 7 files changed, 248 insertions(+), 2 deletions(-) create mode 100644 public/app/plugins/panel/timeseries/overrides/colorSeriesConfigFactory.test.ts create mode 100644 public/app/plugins/panel/timeseries/overrides/colorSeriesConfigFactory.ts rename public/app/plugins/panel/timeseries/{ => overrides}/hideSeriesConfigFactory.test.ts (100%) rename public/app/plugins/panel/timeseries/{ => overrides}/hideSeriesConfigFactory.ts (100%) diff --git a/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx b/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx index 4486e7b2f5f..d995c5a4dbd 100755 --- a/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx +++ b/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx @@ -33,6 +33,7 @@ export interface GraphNGProps extends Omit { legend?: VizLegendOptions; fields?: XYFieldMatchers; // default will assume timeseries data onLegendClick?: (event: GraphNGLegendEvent) => void; + onSeriesColorChange?: (label: string, color: string) => void; } const defaultConfig: GraphFieldConfig = { @@ -51,6 +52,7 @@ export const GraphNG: React.FC = ({ timeRange, timeZone, onLegendClick, + onSeriesColorChange, ...plotProps }) => { const alignedFrameWithGapTest = useMemo(() => alignDataFrames(data, fields), [data, fields]); @@ -222,6 +224,7 @@ export const GraphNG: React.FC = ({ placement={legend!.placement} items={legendItemsRef.current} displayMode={legend!.displayMode} + onSeriesColorChange={onSeriesColorChange} /> ); diff --git a/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx b/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx index 8b4164404ff..7076c6b772e 100644 --- a/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx +++ b/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx @@ -4,8 +4,9 @@ import { PanelProps } from '@grafana/data'; import { Options } from './types'; import { AnnotationsPlugin } from './plugins/AnnotationsPlugin'; import { ExemplarsPlugin } from './plugins/ExemplarsPlugin'; -import { hideSeriesConfigFactory } from './hideSeriesConfigFactory'; import { ContextMenuPlugin } from './plugins/ContextMenuPlugin'; +import { hideSeriesConfigFactory } from './overrides/hideSeriesConfigFactory'; +import { changeSeriesColorConfigFactory } from './overrides/colorSeriesConfigFactory'; interface TimeSeriesPanelProps extends PanelProps {} @@ -28,6 +29,13 @@ export const TimeSeriesPanel: React.FC = ({ [fieldConfig, onFieldConfigChange, data.series] ); + const onSeriesColorChange = useCallback( + (label: string, color: string) => { + onFieldConfigChange(changeSeriesColorConfigFactory(label, color, fieldConfig)); + }, + [fieldConfig, onFieldConfigChange] + ); + return ( = ({ height={height} legend={options.legend} onLegendClick={onLegendClick} + onSeriesColorChange={onSeriesColorChange} > diff --git a/public/app/plugins/panel/timeseries/overrides/colorSeriesConfigFactory.test.ts b/public/app/plugins/panel/timeseries/overrides/colorSeriesConfigFactory.test.ts new file mode 100644 index 00000000000..c66d04ffeb3 --- /dev/null +++ b/public/app/plugins/panel/timeseries/overrides/colorSeriesConfigFactory.test.ts @@ -0,0 +1,151 @@ +import { FieldColorModeId, FieldConfigSource, FieldMatcherID } from '@grafana/data'; +import { changeSeriesColorConfigFactory } from './colorSeriesConfigFactory'; + +describe('changeSeriesColorConfigFactory', () => { + it('should create config override to change color for serie', () => { + const label = 'temperature'; + const color = 'green'; + + const existingConfig: FieldConfigSource = { + defaults: {}, + overrides: [], + }; + + const config = changeSeriesColorConfigFactory(label, color, existingConfig); + + expect(config).toEqual({ + defaults: {}, + overrides: [ + { + matcher: { + id: FieldMatcherID.byName, + options: label, + }, + properties: [ + { + id: 'color', + value: { + mode: FieldColorModeId.Fixed, + fixedColor: color, + }, + }, + ], + }, + ], + }); + }); + + it('should create config override to change color for serie when override already exists for series', () => { + const label = 'temperature'; + const color = 'green'; + + const existingConfig: FieldConfigSource = { + defaults: {}, + overrides: [ + { + matcher: { + id: FieldMatcherID.byName, + options: label, + }, + properties: [ + { + id: 'other', + value: 'other', + }, + ], + }, + ], + }; + + const config = changeSeriesColorConfigFactory(label, color, existingConfig); + + expect(config).toEqual({ + defaults: {}, + overrides: [ + { + matcher: { + id: FieldMatcherID.byName, + options: label, + }, + properties: [ + { + id: 'other', + value: 'other', + }, + { + id: 'color', + value: { + mode: FieldColorModeId.Fixed, + fixedColor: color, + }, + }, + ], + }, + ], + }); + }); + + it('should create config override to change color for serie when override exists for other series', () => { + const label = 'temperature'; + const color = 'green'; + + const existingConfig: FieldConfigSource = { + defaults: {}, + overrides: [ + { + matcher: { + id: FieldMatcherID.byName, + options: 'humidity', + }, + properties: [ + { + id: 'color', + value: { + mode: FieldColorModeId.Fixed, + fixedColor: color, + }, + }, + ], + }, + ], + }; + + const config = changeSeriesColorConfigFactory(label, color, existingConfig); + + expect(config).toEqual({ + defaults: {}, + overrides: [ + { + matcher: { + id: FieldMatcherID.byName, + options: 'humidity', + }, + properties: [ + { + id: 'color', + value: { + mode: FieldColorModeId.Fixed, + fixedColor: color, + }, + }, + ], + }, + { + matcher: { + id: FieldMatcherID.byName, + options: label, + }, + properties: [ + { + id: 'color', + value: { + mode: FieldColorModeId.Fixed, + fixedColor: color, + }, + }, + ], + }, + ], + }); + }); +}); diff --git a/public/app/plugins/panel/timeseries/overrides/colorSeriesConfigFactory.ts b/public/app/plugins/panel/timeseries/overrides/colorSeriesConfigFactory.ts new file mode 100644 index 00000000000..46f67ffbfa1 --- /dev/null +++ b/public/app/plugins/panel/timeseries/overrides/colorSeriesConfigFactory.ts @@ -0,0 +1,74 @@ +import { + ConfigOverrideRule, + DynamicConfigValue, + FieldColorModeId, + FieldConfigSource, + FieldMatcherID, +} from '@grafana/data'; + +export const changeSeriesColorConfigFactory = ( + label: string, + color: string, + fieldConfig: FieldConfigSource +): FieldConfigSource => { + const { overrides } = fieldConfig; + const currentIndex = fieldConfig.overrides.findIndex(override => { + return override.matcher.id === FieldMatcherID.byName && override.matcher.options === label; + }); + + if (currentIndex < 0) { + return { + ...fieldConfig, + overrides: [...fieldConfig.overrides, createOverride(label, color)], + }; + } + + const overridesCopy = Array.from(overrides); + const existing = overridesCopy[currentIndex]; + const propertyIndex = existing.properties.findIndex(p => p.id === 'color'); + + if (propertyIndex < 0) { + overridesCopy[currentIndex] = { + ...existing, + properties: [...existing.properties, createProperty(color)], + }; + + return { + ...fieldConfig, + overrides: overridesCopy, + }; + } + + const propertiesCopy = Array.from(existing.properties); + propertiesCopy[propertyIndex] = createProperty(color); + + overridesCopy[currentIndex] = { + ...existing, + properties: propertiesCopy, + }; + + return { + ...fieldConfig, + overrides: overridesCopy, + }; +}; + +const createOverride = (label: string, color: string): ConfigOverrideRule => { + return { + matcher: { + id: FieldMatcherID.byName, + options: label, + }, + properties: [createProperty(color)], + }; +}; + +const createProperty = (color: string): DynamicConfigValue => { + return { + id: 'color', + value: { + mode: FieldColorModeId.Fixed, + fixedColor: color, + }, + }; +}; diff --git a/public/app/plugins/panel/timeseries/hideSeriesConfigFactory.test.ts b/public/app/plugins/panel/timeseries/overrides/hideSeriesConfigFactory.test.ts similarity index 100% rename from public/app/plugins/panel/timeseries/hideSeriesConfigFactory.test.ts rename to public/app/plugins/panel/timeseries/overrides/hideSeriesConfigFactory.test.ts diff --git a/public/app/plugins/panel/timeseries/hideSeriesConfigFactory.ts b/public/app/plugins/panel/timeseries/overrides/hideSeriesConfigFactory.ts similarity index 100% rename from public/app/plugins/panel/timeseries/hideSeriesConfigFactory.ts rename to public/app/plugins/panel/timeseries/overrides/hideSeriesConfigFactory.ts diff --git a/public/app/plugins/panel/xychart/XYChartPanel.tsx b/public/app/plugins/panel/xychart/XYChartPanel.tsx index 70fcbe4dc07..9771ce28458 100644 --- a/public/app/plugins/panel/xychart/XYChartPanel.tsx +++ b/public/app/plugins/panel/xychart/XYChartPanel.tsx @@ -2,8 +2,9 @@ import React, { useCallback, useMemo } from 'react'; import { Button, TooltipPlugin, GraphNG, GraphNGLegendEvent } from '@grafana/ui'; import { PanelProps } from '@grafana/data'; import { Options } from './types'; -import { hideSeriesConfigFactory } from '../timeseries/hideSeriesConfigFactory'; +import { hideSeriesConfigFactory } from '../timeseries/overrides/hideSeriesConfigFactory'; import { getXYDimensions } from './dims'; +import { changeSeriesColorConfigFactory } from '../timeseries/overrides/colorSeriesConfigFactory'; interface XYChartPanelProps extends PanelProps {} @@ -41,6 +42,13 @@ export const XYChartPanel: React.FC = ({ [fieldConfig, onFieldConfigChange, frames] ); + const onSeriesColorChange = useCallback( + (label: string, color: string) => { + onFieldConfigChange(changeSeriesColorConfigFactory(label, color, fieldConfig)); + }, + [fieldConfig, onFieldConfigChange] + ); + return ( = ({ height={height} legend={options.legend} onLegendClick={onLegendClick} + onSeriesColorChange={onSeriesColorChange} > <>{/* needs to be an array */}