mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
GraphNG: added support to change series color from legend. (#30256)
* added support for changing color of series. * removed dependency on internal type.
This commit is contained in:
parent
e089365abe
commit
382c75d0db
@ -33,6 +33,7 @@ export interface GraphNGProps extends Omit<PlotProps, 'data' | 'config'> {
|
|||||||
legend?: VizLegendOptions;
|
legend?: VizLegendOptions;
|
||||||
fields?: XYFieldMatchers; // default will assume timeseries data
|
fields?: XYFieldMatchers; // default will assume timeseries data
|
||||||
onLegendClick?: (event: GraphNGLegendEvent) => void;
|
onLegendClick?: (event: GraphNGLegendEvent) => void;
|
||||||
|
onSeriesColorChange?: (label: string, color: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultConfig: GraphFieldConfig = {
|
const defaultConfig: GraphFieldConfig = {
|
||||||
@ -51,6 +52,7 @@ export const GraphNG: React.FC<GraphNGProps> = ({
|
|||||||
timeRange,
|
timeRange,
|
||||||
timeZone,
|
timeZone,
|
||||||
onLegendClick,
|
onLegendClick,
|
||||||
|
onSeriesColorChange,
|
||||||
...plotProps
|
...plotProps
|
||||||
}) => {
|
}) => {
|
||||||
const alignedFrameWithGapTest = useMemo(() => alignDataFrames(data, fields), [data, fields]);
|
const alignedFrameWithGapTest = useMemo(() => alignDataFrames(data, fields), [data, fields]);
|
||||||
@ -222,6 +224,7 @@ export const GraphNG: React.FC<GraphNGProps> = ({
|
|||||||
placement={legend!.placement}
|
placement={legend!.placement}
|
||||||
items={legendItemsRef.current}
|
items={legendItemsRef.current}
|
||||||
displayMode={legend!.displayMode}
|
displayMode={legend!.displayMode}
|
||||||
|
onSeriesColorChange={onSeriesColorChange}
|
||||||
/>
|
/>
|
||||||
</VizLayout.Legend>
|
</VizLayout.Legend>
|
||||||
);
|
);
|
||||||
|
@ -4,8 +4,9 @@ import { PanelProps } from '@grafana/data';
|
|||||||
import { Options } from './types';
|
import { Options } from './types';
|
||||||
import { AnnotationsPlugin } from './plugins/AnnotationsPlugin';
|
import { AnnotationsPlugin } from './plugins/AnnotationsPlugin';
|
||||||
import { ExemplarsPlugin } from './plugins/ExemplarsPlugin';
|
import { ExemplarsPlugin } from './plugins/ExemplarsPlugin';
|
||||||
import { hideSeriesConfigFactory } from './hideSeriesConfigFactory';
|
|
||||||
import { ContextMenuPlugin } from './plugins/ContextMenuPlugin';
|
import { ContextMenuPlugin } from './plugins/ContextMenuPlugin';
|
||||||
|
import { hideSeriesConfigFactory } from './overrides/hideSeriesConfigFactory';
|
||||||
|
import { changeSeriesColorConfigFactory } from './overrides/colorSeriesConfigFactory';
|
||||||
|
|
||||||
interface TimeSeriesPanelProps extends PanelProps<Options> {}
|
interface TimeSeriesPanelProps extends PanelProps<Options> {}
|
||||||
|
|
||||||
@ -28,6 +29,13 @@ export const TimeSeriesPanel: React.FC<TimeSeriesPanelProps> = ({
|
|||||||
[fieldConfig, onFieldConfigChange, data.series]
|
[fieldConfig, onFieldConfigChange, data.series]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onSeriesColorChange = useCallback(
|
||||||
|
(label: string, color: string) => {
|
||||||
|
onFieldConfigChange(changeSeriesColorConfigFactory(label, color, fieldConfig));
|
||||||
|
},
|
||||||
|
[fieldConfig, onFieldConfigChange]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GraphNG
|
<GraphNG
|
||||||
data={data.series}
|
data={data.series}
|
||||||
@ -37,6 +45,7 @@ export const TimeSeriesPanel: React.FC<TimeSeriesPanelProps> = ({
|
|||||||
height={height}
|
height={height}
|
||||||
legend={options.legend}
|
legend={options.legend}
|
||||||
onLegendClick={onLegendClick}
|
onLegendClick={onLegendClick}
|
||||||
|
onSeriesColorChange={onSeriesColorChange}
|
||||||
>
|
>
|
||||||
<TooltipPlugin mode={options.tooltipOptions.mode as any} timeZone={timeZone} />
|
<TooltipPlugin mode={options.tooltipOptions.mode as any} timeZone={timeZone} />
|
||||||
<ZoomPlugin onZoom={onChangeTimeRange} />
|
<ZoomPlugin onZoom={onChangeTimeRange} />
|
||||||
|
@ -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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -2,8 +2,9 @@ import React, { useCallback, useMemo } from 'react';
|
|||||||
import { Button, TooltipPlugin, GraphNG, GraphNGLegendEvent } from '@grafana/ui';
|
import { Button, TooltipPlugin, GraphNG, GraphNGLegendEvent } from '@grafana/ui';
|
||||||
import { PanelProps } from '@grafana/data';
|
import { PanelProps } from '@grafana/data';
|
||||||
import { Options } from './types';
|
import { Options } from './types';
|
||||||
import { hideSeriesConfigFactory } from '../timeseries/hideSeriesConfigFactory';
|
import { hideSeriesConfigFactory } from '../timeseries/overrides/hideSeriesConfigFactory';
|
||||||
import { getXYDimensions } from './dims';
|
import { getXYDimensions } from './dims';
|
||||||
|
import { changeSeriesColorConfigFactory } from '../timeseries/overrides/colorSeriesConfigFactory';
|
||||||
|
|
||||||
interface XYChartPanelProps extends PanelProps<Options> {}
|
interface XYChartPanelProps extends PanelProps<Options> {}
|
||||||
|
|
||||||
@ -41,6 +42,13 @@ export const XYChartPanel: React.FC<XYChartPanelProps> = ({
|
|||||||
[fieldConfig, onFieldConfigChange, frames]
|
[fieldConfig, onFieldConfigChange, frames]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onSeriesColorChange = useCallback(
|
||||||
|
(label: string, color: string) => {
|
||||||
|
onFieldConfigChange(changeSeriesColorConfigFactory(label, color, fieldConfig));
|
||||||
|
},
|
||||||
|
[fieldConfig, onFieldConfigChange]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GraphNG
|
<GraphNG
|
||||||
data={frames}
|
data={frames}
|
||||||
@ -51,6 +59,7 @@ export const XYChartPanel: React.FC<XYChartPanelProps> = ({
|
|||||||
height={height}
|
height={height}
|
||||||
legend={options.legend}
|
legend={options.legend}
|
||||||
onLegendClick={onLegendClick}
|
onLegendClick={onLegendClick}
|
||||||
|
onSeriesColorChange={onSeriesColorChange}
|
||||||
>
|
>
|
||||||
<TooltipPlugin mode={options.tooltipOptions.mode as any} timeZone={timeZone} />
|
<TooltipPlugin mode={options.tooltipOptions.mode as any} timeZone={timeZone} />
|
||||||
<>{/* needs to be an array */}</>
|
<>{/* needs to be an array */}</>
|
||||||
|
Loading…
Reference in New Issue
Block a user