Commit 93a59561 by Torkel Ödegaard Committed by GitHub

GraphNG: Color series from by value scheme & change to fillGradient to gradientMode (#29893)

parent f004655b
......@@ -34,7 +34,7 @@
"axisLabel": "",
"axisPlacement": "auto",
"drawStyle": "line",
"fillGradient": 0.5,
"gradientMode": "opacity",
"fillOpacity": 40,
"lineInterpolation": "linear",
"lineWidth": 2,
......@@ -122,7 +122,7 @@
"axisLabel": "",
"axisPlacement": "auto",
"drawStyle": "bars",
"fillGradient": 0.4,
"gradientNode": "opacity",
"fillOpacity": 53,
"lineInterpolation": "linear",
"lineWidth": 1,
......@@ -211,7 +211,7 @@
"axisLabel": "",
"axisPlacement": "auto",
"drawStyle": "bars",
"fillGradient": "opacity",
"gradientMode": "opacity",
"fillOpacity": 100,
"lineInterpolation": "linear",
"lineWidth": 0,
......@@ -300,7 +300,7 @@
"axisLabel": "",
"axisPlacement": "auto",
"drawStyle": "line",
"fillGradient": 0.5,
"gradientMode": "opacity",
"fillOpacity": 20,
"lineInterpolation": "linear",
"lineWidth": 1,
......@@ -388,7 +388,7 @@
"axisLabel": "",
"axisPlacement": "auto",
"drawStyle": "line",
"fillGradient": "hue",
"gradientMode": "hue",
"fillOpacity": 62,
"lineInterpolation": "smooth",
"lineWidth": 2,
......@@ -477,7 +477,7 @@
"axisLabel": "",
"axisPlacement": "auto",
"drawStyle": "bars",
"fillGradient": "hue",
"gradientMode": "hue",
"fillOpacity": 100,
"lineInterpolation": "smooth",
"lineWidth": 0,
......
......@@ -34,7 +34,7 @@
"axisLabel": "",
"axisPlacement": "auto",
"drawStyle": "line",
"fillGradient": 0,
"fillGradient": "none",
"fillOpacity": 0,
"hideFrom": {
"graph": false,
......@@ -135,7 +135,7 @@
"axisLabel": "",
"axisPlacement": "auto",
"drawStyle": "line",
"fillGradient": 0,
"fillGradient": "none",
"fillOpacity": 0,
"hideFrom": {
"graph": false,
......@@ -245,7 +245,7 @@
"axisLabel": "",
"axisPlacement": "auto",
"drawStyle": "line",
"fillGradient": 0,
"fillGradient": "none",
"fillOpacity": 0,
"hideFrom": {
"graph": false,
......@@ -374,7 +374,7 @@
"axisLabel": "",
"axisPlacement": "auto",
"drawStyle": "line",
"fillGradient": 0,
"fillGradient": "none",
"fillOpacity": 0,
"hideFrom": {
"graph": false,
......@@ -475,7 +475,7 @@
"axisLabel": "",
"axisPlacement": "auto",
"drawStyle": "line",
"fillGradient": 0,
"fillGradient": "none",
"fillOpacity": 0,
"hideFrom": {
"graph": false,
......@@ -585,7 +585,7 @@
"axisLabel": "",
"axisPlacement": "auto",
"drawStyle": "line",
"fillGradient": 0,
"fillGradient": "none",
"fillOpacity": 0,
"hideFrom": {
"graph": false,
......@@ -726,9 +726,7 @@
"fill": {
"alpha": 0
},
"fillGradient": {
"label": "None"
},
"fillGradient": "none",
"fillOpacity": 10,
"hideFrom": {
"graph": false,
......@@ -889,9 +887,7 @@
"fill": {
"alpha": 0
},
"fillGradient": {
"label": "None"
},
"fillGradient": "none",
"fillOpacity": 10,
"hideFrom": {
"graph": false,
......
import { Field, FieldColorModeId } from '../types';
import { Field, FieldColorModeId, FieldType } from '../types';
import { getTestTheme } from '../utils/testdata/testTheme';
import { fieldColorModeRegistry, FieldValueColorCalculator } from './fieldColor';
import { ArrayVector } from '../vector/ArrayVector';
import { fieldColorModeRegistry, FieldValueColorCalculator, getFieldSeriesColor } from './fieldColor';
describe('fieldColorModeRegistry', () => {
interface GetCalcOptions {
mode: string;
seriesIndex?: number;
}
function getTestField(mode: string): Field {
return {
name: 'name',
type: FieldType.number,
values: new ArrayVector(),
config: {
color: {
mode: mode,
} as any,
},
state: {},
};
}
interface GetCalcOptions {
mode: string;
seriesIndex?: number;
}
function getCalculator(options: GetCalcOptions): FieldValueColorCalculator {
const mode = fieldColorModeRegistry.get(options.mode);
return mode.getCalculator({ state: { seriesIndex: options.seriesIndex } } as Field, getTestTheme());
}
function getCalculator(options: GetCalcOptions): FieldValueColorCalculator {
const field = getTestField(options.mode);
const mode = fieldColorModeRegistry.get(options.mode);
field.state!.seriesIndex = options.seriesIndex;
return mode.getCalculator(field, getTestTheme());
}
describe('fieldColorModeRegistry', () => {
it('Schemes should interpolate', () => {
const calcFn = getCalculator({ mode: 'continuous-GrYlRd' });
expect(calcFn(70, 0.5, undefined)).toEqual('rgb(226, 192, 61)');
......@@ -27,4 +44,48 @@ describe('fieldColorModeRegistry', () => {
const calcFn = getCalculator({ mode: FieldColorModeId.PaletteClassic, seriesIndex: 1 });
expect(calcFn(70, 0, undefined)).toEqual('#EAB839');
});
it('When color.seriesBy is set to last use that instead of v', () => {
const field = getTestField('continuous-GrYlRd');
field.config.color!.seriesBy = 'last';
// min = -10, max = 10, last = 5
// last percent 75%
field.values = new ArrayVector([0, -10, 5, 10, 2, 5]);
const color = getFieldSeriesColor(field, getTestTheme());
const calcFn = getCalculator({ mode: 'continuous-GrYlRd' });
expect(color.color).toEqual(calcFn(4, 0.75));
});
});
describe('getFieldSeriesColor', () => {
const field = getTestField('continuous-GrYlRd');
field.values = new ArrayVector([0, -10, 5, 10, 2, 5]);
it('When color.seriesBy is last use that to calc series color', () => {
field.config.color!.seriesBy = 'last';
const color = getFieldSeriesColor(field, getTestTheme());
const calcFn = getCalculator({ mode: 'continuous-GrYlRd' });
// the 4 can be anything, 0.75 comes from 5 being 75% in the range -10 to 10 (see data above)
expect(color.color).toEqual(calcFn(4, 0.75));
});
it('When color.seriesBy is max use that to calc series color', () => {
field.config.color!.seriesBy = 'max';
const color = getFieldSeriesColor(field, getTestTheme());
const calcFn = getCalculator({ mode: 'continuous-GrYlRd' });
expect(color.color).toEqual(calcFn(10, 1));
});
it('When color.seriesBy is min use that to calc series color', () => {
field.config.color!.seriesBy = 'min';
const color = getFieldSeriesColor(field, getTestTheme());
const calcFn = getCalculator({ mode: 'continuous-GrYlRd' });
expect(color.color).toEqual(calcFn(-10, 0));
});
});
......@@ -3,6 +3,8 @@ import { classicColors, getColorForTheme, RegistryItem } from '../utils';
import { Registry } from '../utils/Registry';
import { interpolateRgbBasis } from 'd3-interpolate';
import { fallBackTreshold } from './thresholds';
import { getScaleCalculator, ColorScaleValue } from './scale';
import { reduceField } from '../transformations/fieldReducer';
export type FieldValueColorCalculator = (value: number, percent: number, Threshold?: Threshold) => string;
......@@ -209,6 +211,30 @@ export function getFieldColorMode(mode?: FieldColorModeId): FieldColorMode {
return fieldColorModeRegistry.get(mode ?? FieldColorModeId.Thresholds);
}
/**
* @alpha
* Function that will return a series color for any given color mode. If the color mode is a by value color
* mode it will use the field.config.color.seriesBy property to figure out which value to use
*/
export function getFieldSeriesColor(field: Field, theme: GrafanaTheme): ColorScaleValue {
const mode = getFieldColorModeForField(field);
if (!mode.isByValue) {
return {
color: mode.getCalculator(field, theme)(0, 0),
threshold: fallBackTreshold,
percent: 1,
};
}
const scale = getScaleCalculator(field, theme);
const stat = field.config.color?.seriesBy ?? 'last';
const calcs = reduceField({ field, reducers: [stat] });
const value = calcs[stat] ?? 0;
return scale(value);
}
function getFixedColor(field: Field, theme: GrafanaTheme) {
return () => {
return getColorForTheme(field.config.color?.fixedColor ?? FALLBACK_COLOR, theme);
......
......@@ -3,7 +3,13 @@ export * from './displayProcessor';
export * from './standardFieldConfigEditorRegistry';
export * from './overrides/processors';
export { getFieldColorModeForField, getFieldColorMode, fieldColorModeRegistry, FieldColorMode } from './fieldColor';
export {
getFieldColorModeForField,
getFieldColorMode,
fieldColorModeRegistry,
FieldColorMode,
getFieldSeriesColor,
} from './fieldColor';
export { FieldConfigOptionsRegistry } from './FieldConfigOptionsRegistry';
export { sortThresholds, getActiveThreshold } from './thresholds';
export { applyFieldOverrides, validateFieldConfig, applyRawFieldOverrides } from './fieldOverrides';
......
......@@ -131,6 +131,11 @@ export interface FieldColorConfigSettings {
* to from thresholds if it was set to a by series palette
*/
preferThresholdsMode?: boolean;
/**
* Set to true if the visualization supports both by value and by series
* This will enable the Color by series UI option that sets the `color.seriesBy` option.
*/
bySeriesSupport?: boolean;
}
export interface StatsPickerConfigSettings {
......
......@@ -4,13 +4,13 @@ import { Field, FieldConfig, FieldType, GrafanaTheme, NumericRange, Threshold }
import { getFieldColorModeForField } from './fieldColor';
import { getActiveThresholdForValue } from './thresholds';
export interface ScaledValue {
export interface ColorScaleValue {
percent: number; // 0-1
threshold: Threshold;
color: string;
}
export type ScaleCalculator = (value: number) => ScaledValue;
export type ScaleCalculator = (value: number) => ColorScaleValue;
export function getScaleCalculator(field: Field, theme: GrafanaTheme): ScaleCalculator {
const mode = getFieldColorModeForField(field);
......
......@@ -18,5 +18,5 @@ export {
BasicValueMatcherOptions,
RangeValueMatcherOptions,
} from './transformations/matchers/valueMatchers/types';
export { PanelPlugin } from './panel/PanelPlugin';
export { PanelPlugin, SetFieldConfigOptionsArgs } from './panel/PanelPlugin';
export { createFieldConfigRegistry } from './panel/registryFactories';
......@@ -21,6 +21,7 @@ type StandardOptionConfig = {
settings?: any;
};
/** @beta */
export interface SetFieldConfigOptionsArgs<TFieldConfigOptions = any> {
/**
* Configuration object of the standard field config properites
......
/**
* @public
*/
export enum FieldColorModeId {
Thresholds = 'thresholds',
PaletteClassic = 'palette-classic',
......@@ -5,9 +8,21 @@ export enum FieldColorModeId {
Fixed = 'fixed',
}
/**
* @public
*/
export interface FieldColor {
/** The main color scheme mode */
mode: FieldColorModeId;
/** Stores the fixed color value if mode is fixed */
fixedColor?: string;
/** Some visualizations need to know how to assign a series color from by value color schemes */
seriesBy?: FieldColorSeriesByMode;
}
/**
* @beta
*/
export type FieldColorSeriesByMode = 'min' | 'max' | 'last';
export const FALLBACK_COLOR = 'gray';
......@@ -8,7 +8,6 @@ import {
fieldReducers,
FieldType,
formattedValueToString,
getFieldColorModeForField,
getFieldDisplayName,
reduceField,
TimeRange,
......@@ -23,6 +22,7 @@ import { LegendDisplayMode, VizLegendItem, VizLegendOptions } from '../VizLegend
import { VizLegend } from '../VizLegend/VizLegend';
import { UPlotConfigBuilder } from '../uPlot/config/UPlotConfigBuilder';
import { useRevision } from '../uPlot/hooks';
import { getFieldColorModeForField, getFieldSeriesColor } from '@grafana/data';
import { GraphNGLegendEvent, GraphNGLegendEventMode } from './types';
import { isNumber } from 'lodash';
......@@ -109,6 +109,7 @@ export const GraphNG: React.FC<GraphNGProps> = ({
// X is the first field in the aligned frame
const xField = alignedFrame.fields[0];
if (xField.type === FieldType.time) {
builder.addScale({
scaleKey: 'x',
......@@ -118,6 +119,7 @@ export const GraphNG: React.FC<GraphNGProps> = ({
return [r.from.valueOf(), r.to.valueOf()];
},
});
builder.addAxis({
scaleKey: 'x',
isTime: true,
......@@ -130,6 +132,7 @@ export const GraphNG: React.FC<GraphNGProps> = ({
builder.addScale({
scaleKey: 'x',
});
builder.addAxis({
scaleKey: 'x',
placement: AxisPlacement.Bottom,
......@@ -153,7 +156,8 @@ export const GraphNG: React.FC<GraphNGProps> = ({
const fmt = field.display ?? defaultFormatter;
const scaleKey = config.unit || FIXED_UNIT;
const colorMode = getFieldColorModeForField(field);
const seriesColor = colorMode.getCalculator(field, theme)(0, 0);
const scaleColor = getFieldSeriesColor(field, theme);
const seriesColor = scaleColor.color;
if (customConfig.axisPlacement !== AxisPlacement.Hidden) {
// The builder will manage unique scaleKeys and combine where appropriate
......@@ -200,18 +204,21 @@ export const GraphNG: React.FC<GraphNGProps> = ({
builder.addSeries({
scaleKey,
showPoints,
colorMode,
fillOpacity,
theme,
drawStyle: customConfig.drawStyle!,
lineColor: customConfig.lineColor ?? seriesColor,
lineWidth: customConfig.lineWidth,
lineInterpolation: customConfig.lineInterpolation,
lineStyle: customConfig.lineStyle,
showPoints,
pointSize: customConfig.pointSize,
pointColor: customConfig.pointColor ?? seriesColor,
fillOpacity,
spanNulls: customConfig.spanNulls || false,
show: !customConfig.hideFrom?.graph,
fillGradient: customConfig.fillGradient,
gradientMode: customConfig.gradientMode,
thresholds: config.thresholds,
// The following properties are not used in the uPlot config, but are utilized as transport for legend config
dataFrameFieldIndex,
......
......@@ -9,11 +9,15 @@ import {
GrafanaTheme,
getColorForTheme,
FieldColorConfigSettings,
FieldColorSeriesByMode,
getFieldColorMode,
} from '@grafana/data';
import { Select } from '../Select/Select';
import { ColorValueEditor } from './color';
import { useStyles, useTheme } from '../../themes/ThemeContext';
import { css } from 'emotion';
import { Field } from '../Forms/Field';
import { RadioButtonGroup } from '../Forms/RadioButtonGroup/RadioButtonGroup';
export const FieldColorEditor: React.FC<FieldConfigEditorProps<FieldColor | undefined, FieldColorConfigSettings>> = ({
value,
......@@ -23,6 +27,7 @@ export const FieldColorEditor: React.FC<FieldConfigEditorProps<FieldColor | unde
const theme = useTheme();
const styles = useStyles(getStyles);
const colorMode = getFieldColorMode(value?.mode);
const availableOptions = item.settings?.byValueSupport
? fieldColorModeRegistry.list()
: fieldColorModeRegistry.list().filter(m => !m.isByValue);
......@@ -44,17 +49,27 @@ export const FieldColorEditor: React.FC<FieldConfigEditorProps<FieldColor | unde
const onModeChange = (newMode: SelectableValue<string>) => {
onChange({
...value,
mode: newMode.value! as FieldColorModeId,
});
};
const onColorChange = (color?: string) => {
onChange({
...value,
mode,
fixedColor: color,
});
};
const onSeriesModeChange = (seriesBy?: FieldColorSeriesByMode) => {
onChange({
...value,
mode,
seriesBy,
});
};
const mode = value?.mode ?? FieldColorModeId.Thresholds;
if (mode === FieldColorModeId.Fixed) {
......@@ -66,6 +81,25 @@ export const FieldColorEditor: React.FC<FieldConfigEditorProps<FieldColor | unde
);
}
if (item.settings?.bySeriesSupport && colorMode.isByValue) {
const seriesModes: Array<SelectableValue<FieldColorSeriesByMode>> = [
{ label: 'Last', value: 'last' },
{ label: 'Min', value: 'min' },
{ label: 'Max', value: 'max' },
];
return (
<>
<div style={{ marginBottom: theme.spacing.formInputMargin }}>
<Select minMenuHeight={200} options={options} value={mode} onChange={onModeChange} />
</div>
<Field label="Color series by">
<RadioButtonGroup value={value?.seriesBy ?? 'last'} options={seriesModes} onChange={onSeriesModeChange} />
</Field>
</>
);
}
return <Select minMenuHeight={200} options={options} value={mode} onChange={onModeChange} />;
};
......
......@@ -137,6 +137,7 @@ export class Sparkline extends PureComponent<Props, State> {
builder.addSeries({
scaleKey,
theme,
fieldName: getFieldDisplayName(field, data),
drawStyle: customConfig.drawStyle!,
lineColor: customConfig.lineColor ?? seriesColor,
......
......@@ -73,17 +73,17 @@ export interface LineConfig {
export interface FillConfig {
fillColor?: string;
fillOpacity?: number;
fillGradient?: FillGradientMode;
fillBelowTo?: string; // name of the field
}
/**
* @alpha
*/
export enum FillGradientMode {
export enum GraphGradientMode {
None = 'none',
Opacity = 'opacity',
Hue = 'hue',
Scheme = 'scheme',
}
/**
......@@ -131,6 +131,7 @@ export interface HideSeriesConfig {
*/
export interface GraphFieldConfig extends LineConfig, FillConfig, PointsConfig, AxisConfig {
drawStyle?: DrawStyle;
gradientMode?: GraphGradientMode;
hideFrom?: HideSeriesConfig;
}
......@@ -165,8 +166,9 @@ export const graphFieldOptions = {
] as Array<SelectableValue<AxisPlacement>>,
fillGradient: [
{ label: 'None', value: FillGradientMode.None },
{ label: 'Opacity', value: FillGradientMode.Opacity },
{ label: 'Hue', value: FillGradientMode.Hue },
] as Array<SelectableValue<FillGradientMode>>,
{ label: 'None', value: GraphGradientMode.None },
{ label: 'Opacity', value: GraphGradientMode.Opacity },
{ label: 'Hue', value: GraphGradientMode.Hue },
// { label: 'Color scheme', value: GraphGradientMode.Scheme },
] as Array<SelectableValue<GraphGradientMode>>,
};
......@@ -2,8 +2,7 @@
import { UPlotConfigBuilder } from './UPlotConfigBuilder';
import { GrafanaTheme } from '@grafana/data';
import { expect } from '../../../../../../public/test/lib/common';
import { FillGradientMode, AxisPlacement, DrawStyle, PointVisibility, ScaleDistribution } from '../config';
import { GraphGradientMode, AxisPlacement, DrawStyle, PointVisibility, ScaleDistribution } from '../config';
import darkTheme from '../../../themes/dark';
describe('UPlotConfigBuilder', () => {
......@@ -327,6 +326,7 @@ describe('UPlotConfigBuilder', () => {
scaleKey: 'scale-x',
fieldName: 'A-series',
lineColor: '#0000ff',
theme: darkTheme,
});
expect(builder.getConfig().series[1].fill).toBe(undefined);
......@@ -340,6 +340,7 @@ describe('UPlotConfigBuilder', () => {
fieldName: 'A-series',
lineColor: '#FFAABB',
fillOpacity: 50,
theme: darkTheme,
});
expect(builder.getConfig().series[1].fill).toBe('rgba(255, 170, 187, 0.5)');
......@@ -354,6 +355,7 @@ describe('UPlotConfigBuilder', () => {
lineColor: '#FFAABB',
fillOpacity: 50,
fillColor: '#FF0000',
theme: darkTheme,
});
expect(builder.getConfig().series[1].fill).toBe('#FF0000');
......@@ -367,7 +369,8 @@ describe('UPlotConfigBuilder', () => {
fieldName: 'A-series',
lineColor: '#FFAABB',
fillOpacity: 50,
fillGradient: FillGradientMode.Opacity,
gradientMode: GraphGradientMode.Opacity,
theme: darkTheme,
});
expect(builder.getConfig().series[1].fill).toBeInstanceOf(Function);
......@@ -380,13 +383,14 @@ describe('UPlotConfigBuilder', () => {
scaleKey: 'scale-x',
fieldName: 'A-series',
fillOpacity: 50,
fillGradient: FillGradientMode.Opacity,
gradientMode: GraphGradientMode.Opacity,
showPoints: PointVisibility.Auto,
pointSize: 5,
pointColor: '#00ff00',
lineColor: '#0000ff',
lineWidth: 1,
spanNulls: false,
theme: darkTheme,
});
expect(builder.getConfig()).toMatchInlineSnapshot(`
......
import { FALLBACK_COLOR, FieldColorMode, GrafanaTheme, ThresholdsConfig } from '@grafana/data';
import tinycolor from 'tinycolor2';
import uPlot, { Series } from 'uplot';
import { getCanvasContext } from '../../../utils/measureText';
import {
DrawStyle,
LineConfig,
......@@ -8,18 +8,25 @@ import {
PointsConfig,
PointVisibility,
LineInterpolation,
FillGradientMode,
GraphGradientMode,
} from '../config';
import { PlotConfigBuilder } from '../types';
import { DataFrameFieldIndex } from '@grafana/data';
import { getScaleGradientFn, getOpacityGradientFn, getHueGradientFn } from './gradientFills';
export interface SeriesProps extends LineConfig, FillConfig, PointsConfig {
scaleKey: string;
gradientMode?: GraphGradientMode;
/** Used when gradientMode is set to Scheme */
thresholds?: ThresholdsConfig;
/** Used when gradientMode is set to Scheme */
colorMode?: FieldColorMode;
fieldName: string;
drawStyle: DrawStyle;
show?: boolean;
dataFrameFieldIndex?: DataFrameFieldIndex;
hideInLegend?: boolean;
theme: GrafanaTheme;
}
export class UPlotSeriesBuilder extends PlotConfigBuilder<SeriesProps, Series> {
......@@ -27,7 +34,6 @@ export class UPlotSeriesBuilder extends PlotConfigBuilder<SeriesProps, Series> {
const {
drawStyle,
lineInterpolation,
lineColor,
lineWidth,
lineStyle,
showPoints,
......@@ -43,7 +49,7 @@ export class UPlotSeriesBuilder extends PlotConfigBuilder<SeriesProps, Series> {
if (drawStyle === DrawStyle.Points) {
lineConfig.paths = () => null;
} else {
lineConfig.stroke = lineColor;
lineConfig.stroke = this.getLineColor();
lineConfig.width = lineWidth;
if (lineStyle && lineStyle.fill !== 'solid') {
if (lineStyle.fill === 'dot') {
......@@ -90,28 +96,39 @@ export class UPlotSeriesBuilder extends PlotConfigBuilder<SeriesProps, Series> {
};
}
getFill(): Series.Fill | undefined {
const { lineColor, fillColor, fillGradient, fillOpacity } = this.props;
private getLineColor(): Series.Stroke {
const { lineColor, gradientMode, colorMode, thresholds } = this.props;
if (fillColor) {
return fillColor;
if (gradientMode === GraphGradientMode.Scheme) {
return getScaleGradientFn(1, colorMode, thresholds);
}
const mode = fillGradient ?? FillGradientMode.None;
let fillOpacityNumber = fillOpacity ?? 0;
return lineColor ?? FALLBACK_COLOR;
}
if (mode !== FillGradientMode.None) {
return getCanvasGradient({
color: (fillColor ?? lineColor)!,
opacity: fillOpacityNumber / 100,
mode,
});
private getFill(): Series.Fill | undefined {
const { lineColor, fillColor, gradientMode, fillOpacity, colorMode, thresholds, theme } = this.props;
if (fillColor) {
return fillColor;
}
if (fillOpacityNumber > 0) {
return tinycolor(lineColor)
.setAlpha(fillOpacityNumber / 100)
.toString();
const mode = gradientMode ?? GraphGradientMode.None;
const opacityPercent = (fillOpacity ?? 0) / 100;
switch (mode) {
case GraphGradientMode.Opacity:
return getOpacityGradientFn((fillColor ?? lineColor)!, opacityPercent);
case GraphGradientMode.Hue:
return getHueGradientFn((fillColor ?? lineColor)!, opacityPercent, theme);
case GraphGradientMode.Scheme:
return getScaleGradientFn(opacityPercent, colorMode, thresholds);
default:
if (opacityPercent > 0) {
return tinycolor(lineColor)
.setAlpha(opacityPercent)
.toString();
}
}
return undefined;
......@@ -165,50 +182,3 @@ function mapDrawStyleToPathBuilder(
return builders.linear; // the default
}
interface AreaGradientOptions {
color: string;
mode: FillGradientMode;
opacity: number;
}
function getCanvasGradient(opts: AreaGradientOptions): (self: uPlot, seriesIdx: number) => CanvasGradient {
return (plot: uPlot, seriesIdx: number) => {
const { color, mode, opacity } = opts;
const ctx = getCanvasContext();
const gradient = ctx.createLinearGradient(0, plot.bbox.top, 0, plot.bbox.top + plot.bbox.height);
switch (mode) {
case FillGradientMode.Hue:
const color1 = tinycolor(color)
.spin(-25)
.darken(30)
.setAlpha(opacity)
.toRgbString();
const color2 = tinycolor(color)
.spin(25)
.lighten(35)
.setAlpha(opacity)
.toRgbString();
gradient.addColorStop(0, color2);
gradient.addColorStop(1, color1);
case FillGradientMode.Opacity:
default:
gradient.addColorStop(
0,
tinycolor(color)
.setAlpha(opacity)
.toRgbString()
);
gradient.addColorStop(
1,
tinycolor(color)
.setAlpha(0)
.toRgbString()
);
return gradient;
}
};
}
import { FieldColorMode, getColorForTheme, GrafanaTheme, ThresholdsConfig } from '@grafana/data';
import tinycolor from 'tinycolor2';
import uPlot from 'uplot';
import darkTheme from '../../../themes/dark';
import { getCanvasContext } from '../../../utils/measureText';
export function getOpacityGradientFn(
color: string,
opacity: number
): (self: uPlot, seriesIdx: number) => CanvasGradient {
return (plot: uPlot, seriesIdx: number) => {
const ctx = getCanvasContext();
const gradient = ctx.createLinearGradient(0, plot.bbox.top, 0, plot.bbox.top + plot.bbox.height);
gradient.addColorStop(
0,
tinycolor(color)
.setAlpha(opacity)
.toRgbString()
);
gradient.addColorStop(
1,
tinycolor(color)
.setAlpha(0)
.toRgbString()
);
return gradient;
};
}
export function getHueGradientFn(
color: string,
opacity: number,
theme: GrafanaTheme
): (self: uPlot, seriesIdx: number) => CanvasGradient {
return (plot: uPlot, seriesIdx: number) => {
const ctx = getCanvasContext();
const gradient = ctx.createLinearGradient(0, plot.bbox.top, 0, plot.bbox.top + plot.bbox.height);
const color1 = tinycolor(color).spin(-15);
const color2 = tinycolor(color).spin(15);
if (theme.isDark) {
gradient.addColorStop(
0,
color2
.lighten(10)
.setAlpha(opacity)
.toString()
);
gradient.addColorStop(
1,
color1
.darken(10)
.setAlpha(opacity)
.toString()
);
} else {
gradient.addColorStop(
0,
color2
.lighten(10)
.setAlpha(opacity)
.toString()
);
gradient.addColorStop(1, color1.setAlpha(opacity).toString());
}
return gradient;
};
}
/**
* Experimental & quick and dirty test
* Not being used
*/
export function getScaleGradientFn(
opacity: number,
colorMode?: FieldColorMode,
thresholds?: ThresholdsConfig
): (self: uPlot, seriesIdx: number) => CanvasGradient {
if (!colorMode) {
throw Error('Missing colorMode required for color scheme gradients');
}
if (!thresholds) {
throw Error('Missing thresholds required for color scheme gradients');
}
return (plot: uPlot, seriesIdx: number) => {
const ctx = getCanvasContext();
const gradient = ctx.createLinearGradient(0, plot.bbox.top, 0, plot.bbox.top + plot.bbox.height);
const series = plot.series[seriesIdx];
const scale = plot.scales[series.scale!];
const range = plot.bbox.height;
console.log('scale', scale);
console.log('series.min', series.min);
console.log('series.max', series.max);
const getColorWithAlpha = (color: string) => {
return tinycolor(getColorForTheme(color, darkTheme))
.setAlpha(opacity)
.toString();
};
const addColorStop = (value: number, color: string) => {
const pos = plot.valToPos(value, series.scale!);
const percent = pos / range;
console.log(`addColorStop(value = ${value}, xPos=${pos})`);
gradient.addColorStop(Math.min(percent, 1), getColorWithAlpha(color));
};
for (let idx = 0; idx < thresholds.steps.length; idx++) {
const step = thresholds.steps[idx];
const value = step.value === -Infinity ? 0 : step.value;
addColorStop(value, step.color);
// to make the gradient discrete
if (thresholds.steps.length > idx + 1) {
addColorStop(thresholds.steps[idx + 1].value - 0.0000001, step.color);
}
}
return gradient;
};
}
......@@ -89,8 +89,8 @@ Object {
"custom": Object {
"axisPlacement": "hidden",
"drawStyle": "line",
"fillGradient": "opacity",
"fillOpacity": 60,
"gradientMode": "opacity",
"lineInterpolation": "stepAfter",
"lineWidth": 1,
"pointSize": 6,
......
......@@ -3,6 +3,7 @@ import {
FieldConfigProperty,
FieldType,
identityOverrideProcessor,
SetFieldConfigOptionsArgs,
stringOverrideProcessor,
} from '@grafana/data';
import {
......@@ -15,12 +16,11 @@ import {
PointVisibility,
ScaleDistribution,
ScaleDistributionConfig,
GraphGradientMode,
} from '@grafana/ui';
import { SeriesConfigEditor } from './HideSeriesConfigEditor';
import { ScaleDistributionEditor } from './ScaleDistributionEditor';
import { LineStyleEditor } from './LineStyleEditor';
import { SetFieldConfigOptionsArgs } from '@grafana/data/src/panel/PanelPlugin';
import { FillGradientMode } from '@grafana/ui/src/components/uPlot/config';
import { FillBellowToEditor } from './FillBelowToEditor';
export const defaultGraphConfig: GraphFieldConfig = {
......@@ -28,7 +28,7 @@ export const defaultGraphConfig: GraphFieldConfig = {
lineInterpolation: LineInterpolation.Linear,
lineWidth: 1,
fillOpacity: 0,
fillGradient: FillGradientMode.None,
gradientMode: GraphGradientMode.None,
};
export function getGraphFieldConfig(cfg: GraphFieldConfig): SetFieldConfigOptionsArgs<GraphFieldConfig> {
......@@ -37,6 +37,8 @@ export function getGraphFieldConfig(cfg: GraphFieldConfig): SetFieldConfigOption
[FieldConfigProperty.Color]: {
settings: {
byValueSupport: false,
bySeriesSupport: true,
preferThresholdsMode: false,
},
defaultValue: {
mode: FieldColorModeId.PaletteClassic,
......@@ -85,13 +87,13 @@ export function getGraphFieldConfig(cfg: GraphFieldConfig): SetFieldConfigOption
showIf: c => c.drawStyle !== DrawStyle.Points,
})
.addRadio({
path: 'fillGradient',
name: 'Fill gradient',
defaultValue: graphFieldOptions.fillGradient[0].value,
path: 'gradientMode',
name: 'Gradient mode',
defaultValue: graphFieldOptions.fillGradient[0],
settings: {
options: graphFieldOptions.fillGradient,
},
showIf: c => !!(c.drawStyle !== DrawStyle.Points && c.fillOpacity && c.fillOpacity > 0),
showIf: c => c.drawStyle !== DrawStyle.Points,
})
.addCustomEditor({
id: 'fillBelowTo',
......
......@@ -12,9 +12,9 @@ import {
} from '@grafana/data';
import { GraphFieldConfig, LegendDisplayMode } from '@grafana/ui';
import {
GraphGradientMode,
AxisPlacement,
DrawStyle,
FillGradientMode,
LineInterpolation,
LineStyle,
PointVisibility,
......@@ -251,7 +251,7 @@ export function flotToGraphOptions(angular: any): { fieldConfig: FieldConfigSour
}
if (isNumber(angular.fillGradient) && angular.fillGradient > 0) {
graph.fillGradient = FillGradientMode.Opacity;
graph.gradientMode = GraphGradientMode.Opacity;
graph.fillOpacity = angular.fillGradient * 10; // fill is 0-10
}
......
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