Commit 382c75d0 by Marcus Andersson Committed by GitHub

GraphNG: added support to change series color from legend. (#30256)

* added support for changing color of series.

* removed dependency on internal type.
parent e089365a
......@@ -33,6 +33,7 @@ export interface GraphNGProps extends Omit<PlotProps, 'data' | 'config'> {
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<GraphNGProps> = ({
timeRange,
timeZone,
onLegendClick,
onSeriesColorChange,
...plotProps
}) => {
const alignedFrameWithGapTest = useMemo(() => alignDataFrames(data, fields), [data, fields]);
......@@ -222,6 +224,7 @@ export const GraphNG: React.FC<GraphNGProps> = ({
placement={legend!.placement}
items={legendItemsRef.current}
displayMode={legend!.displayMode}
onSeriesColorChange={onSeriesColorChange}
/>
</VizLayout.Legend>
);
......
......@@ -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<Options> {}
......@@ -28,6 +29,13 @@ export const TimeSeriesPanel: React.FC<TimeSeriesPanelProps> = ({
[fieldConfig, onFieldConfigChange, data.series]
);
const onSeriesColorChange = useCallback(
(label: string, color: string) => {
onFieldConfigChange(changeSeriesColorConfigFactory(label, color, fieldConfig));
},
[fieldConfig, onFieldConfigChange]
);
return (
<GraphNG
data={data.series}
......@@ -37,6 +45,7 @@ export const TimeSeriesPanel: React.FC<TimeSeriesPanelProps> = ({
height={height}
legend={options.legend}
onLegendClick={onLegendClick}
onSeriesColorChange={onSeriesColorChange}
>
<TooltipPlugin mode={options.tooltipOptions.mode as any} timeZone={timeZone} />
<ZoomPlugin onZoom={onChangeTimeRange} />
......
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,
},
},
],
},
],
});
});
});
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,
},
};
};
......@@ -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<Options> {}
......@@ -41,6 +42,13 @@ export const XYChartPanel: React.FC<XYChartPanelProps> = ({
[fieldConfig, onFieldConfigChange, frames]
);
const onSeriesColorChange = useCallback(
(label: string, color: string) => {
onFieldConfigChange(changeSeriesColorConfigFactory(label, color, fieldConfig));
},
[fieldConfig, onFieldConfigChange]
);
return (
<GraphNG
data={frames}
......@@ -51,6 +59,7 @@ export const XYChartPanel: React.FC<XYChartPanelProps> = ({
height={height}
legend={options.legend}
onLegendClick={onLegendClick}
onSeriesColorChange={onSeriesColorChange}
>
<TooltipPlugin mode={options.tooltipOptions.mode as any} timeZone={timeZone} />
<>{/* needs to be an array */}</>
......
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