TimeSeries: Use graph min/max for percentage threshold (#38528)

* TimeSeries: Use graph min/max as baseis for percentage threshold calculation.

* respect hard and soft axis limits for % threshold steps

* revert state-timeline changes

* enable by-threshold coloring in histogram and barchart

* revert yMin

Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
This commit is contained in:
Oscar Kilhed 2021-08-29 22:46:17 +02:00 committed by GitHub
parent 96557ecadc
commit b248c119ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 77 additions and 14 deletions

View File

@ -236,6 +236,10 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{ sync: DashboardCursor
show: !customConfig.hideFrom?.viz, show: !customConfig.hideFrom?.viz,
gradientMode: customConfig.gradientMode, gradientMode: customConfig.gradientMode,
thresholds: config.thresholds, thresholds: config.thresholds,
hardMin: field.config.min,
hardMax: field.config.max,
softMin: customConfig.axisSoftMin,
softMax: customConfig.axisSoftMax,
// The following properties are not used in the uPlot config, but are utilized as transport for legend config // The following properties are not used in the uPlot config, but are utilized as transport for legend config
dataFrameFieldIndex: field.state?.origin, dataFrameFieldIndex: field.state?.origin,
}); });
@ -249,6 +253,10 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{ sync: DashboardCursor
thresholds: config.thresholds, thresholds: config.thresholds,
scaleKey, scaleKey,
theme, theme,
hardMin: field.config.min,
hardMax: field.config.max,
softMin: customConfig.axisSoftMin,
softMax: customConfig.axisSoftMax,
}); });
} }
} }

View File

@ -26,10 +26,15 @@ export interface SeriesProps extends LineConfig, BarConfig, FillConfig, PointsCo
scaleKey: string; scaleKey: string;
pxAlign?: boolean; pxAlign?: boolean;
gradientMode?: GraphGradientMode; gradientMode?: GraphGradientMode;
/** Used when gradientMode is set to Scheme */ /** Used when gradientMode is set to Scheme */
thresholds?: ThresholdsConfig; thresholds?: ThresholdsConfig;
/** Used when gradientMode is set to Scheme */
colorMode?: FieldColorMode; colorMode?: FieldColorMode;
hardMin?: number | null;
hardMax?: number | null;
softMin?: number | null;
softMax?: number | null;
drawStyle?: GraphDrawStyle; drawStyle?: GraphDrawStyle;
pathBuilder?: Series.PathBuilder; pathBuilder?: Series.PathBuilder;
pointsFilter?: Series.Points.Filter; pointsFilter?: Series.Points.Filter;
@ -138,17 +143,29 @@ export class UPlotSeriesBuilder extends PlotConfigBuilder<SeriesProps, Series> {
} }
private getLineColor(): Series.Stroke { private getLineColor(): Series.Stroke {
const { lineColor, gradientMode, colorMode, thresholds, theme } = this.props; const { lineColor, gradientMode, colorMode, thresholds, theme, hardMin, hardMax, softMin, softMax } = this.props;
if (gradientMode === GraphGradientMode.Scheme && colorMode?.id !== FieldColorModeId.Fixed) { if (gradientMode === GraphGradientMode.Scheme && colorMode?.id !== FieldColorModeId.Fixed) {
return getScaleGradientFn(1, theme, colorMode, thresholds); return getScaleGradientFn(1, theme, colorMode, thresholds, hardMin, hardMax, softMin, softMax);
} }
return lineColor ?? FALLBACK_COLOR; return lineColor ?? FALLBACK_COLOR;
} }
private getFill(): Series.Fill | undefined { private getFill(): Series.Fill | undefined {
const { lineColor, fillColor, gradientMode, fillOpacity, colorMode, thresholds, theme } = this.props; const {
lineColor,
fillColor,
gradientMode,
fillOpacity,
colorMode,
thresholds,
theme,
hardMin,
hardMax,
softMin,
softMax,
} = this.props;
if (fillColor) { if (fillColor) {
return fillColor; return fillColor;
@ -164,7 +181,7 @@ export class UPlotSeriesBuilder extends PlotConfigBuilder<SeriesProps, Series> {
return getHueGradientFn((fillColor ?? lineColor)!, opacityPercent, theme); return getHueGradientFn((fillColor ?? lineColor)!, opacityPercent, theme);
case GraphGradientMode.Scheme: case GraphGradientMode.Scheme:
if (colorMode?.id !== FieldColorModeId.Fixed) { if (colorMode?.id !== FieldColorModeId.Fixed) {
return getScaleGradientFn(opacityPercent, theme, colorMode, thresholds); return getScaleGradientFn(opacityPercent, theme, colorMode, thresholds, hardMin, hardMax, softMin, softMax);
} }
// intentional fall-through to handle Scheme with Fixed color // intentional fall-through to handle Scheme with Fixed color
default: default:

View File

@ -1,6 +1,6 @@
import { GrafanaTheme2, ThresholdsConfig, ThresholdsMode } from '@grafana/data'; import { GrafanaTheme2, ThresholdsConfig, ThresholdsMode } from '@grafana/data';
import { GraphThresholdsStyleConfig, GraphTresholdsStyleMode } from '@grafana/schema'; import { GraphThresholdsStyleConfig, GraphTresholdsStyleMode } from '@grafana/schema';
import { getDataRange, GradientDirection, scaleGradient } from './gradientFills'; import { getThresholdRange, GradientDirection, scaleGradient } from './gradientFills';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
export interface UPlotThresholdOptions { export interface UPlotThresholdOptions {
@ -8,23 +8,27 @@ export interface UPlotThresholdOptions {
thresholds: ThresholdsConfig; thresholds: ThresholdsConfig;
config: GraphThresholdsStyleConfig; config: GraphThresholdsStyleConfig;
theme: GrafanaTheme2; theme: GrafanaTheme2;
hardMin?: number | null;
hardMax?: number | null;
softMin?: number | null;
softMax?: number | null;
} }
export function getThresholdsDrawHook(options: UPlotThresholdOptions) { export function getThresholdsDrawHook(options: UPlotThresholdOptions) {
return (u: uPlot) => { return (u: uPlot) => {
const ctx = u.ctx; const ctx = u.ctx;
const { scaleKey, thresholds, theme, config } = options; const { scaleKey, thresholds, theme, config, hardMin, hardMax, softMin, softMax } = options;
const { min: xMin, max: xMax } = u.scales.x; const { min: xMin, max: xMax } = u.scales.x;
const { min: yMin, max: yMax } = u.scales[scaleKey]; const { min: yMin, max: yMax } = u.scales[scaleKey];
if (xMin === undefined || xMax === undefined || yMin === undefined || yMax === undefined) { if (xMin == null || xMax == null || yMin == null || yMax == null) {
return; return;
} }
let { steps, mode } = thresholds; let { steps, mode } = thresholds;
if (mode === ThresholdsMode.Percentage) { if (mode === ThresholdsMode.Percentage) {
let [min, max] = getDataRange(u, scaleKey); let [min, max] = getThresholdRange(u, scaleKey, hardMin, hardMax, softMin, softMax);
let range = max - min; let range = max - min;
steps = steps.map((step) => ({ steps = steps.map((step) => ({

View File

@ -172,11 +172,36 @@ export function getDataRange(plot: uPlot, scaleKey: string) {
return [min, max]; return [min, max];
} }
export function getThresholdRange(
u: uPlot,
scaleKey: string,
hardMin?: number | null,
hardMax?: number | null,
softMin?: number | null,
softMax?: number | null
) {
let min = hardMin ?? softMin ?? null;
let max = hardMax ?? softMax ?? null;
if (min == null || max == null) {
let [dataMin, dataMax] = getDataRange(u, scaleKey);
min = min ?? dataMin ?? 0;
max = max ?? dataMax ?? 100;
}
return [min, max];
}
export function getScaleGradientFn( export function getScaleGradientFn(
opacity: number, opacity: number,
theme: GrafanaTheme2, theme: GrafanaTheme2,
colorMode?: FieldColorMode, colorMode?: FieldColorMode,
thresholds?: ThresholdsConfig thresholds?: ThresholdsConfig,
hardMin?: number | null,
hardMax?: number | null,
softMin?: number | null,
softMax?: number | null
): (self: uPlot, seriesIdx: number) => CanvasGradient | string { ): (self: uPlot, seriesIdx: number) => CanvasGradient | string {
if (!colorMode) { if (!colorMode) {
throw Error('Missing colorMode required for color scheme gradients'); throw Error('Missing colorMode required for color scheme gradients');
@ -199,7 +224,7 @@ export function getScaleGradientFn(
); );
gradient = scaleGradient(plot, scaleKey, GradientDirection.Up, valueStops, true); gradient = scaleGradient(plot, scaleKey, GradientDirection.Up, valueStops, true);
} else { } else {
const [min, max] = getDataRange(plot, scaleKey); const [min, max] = getThresholdRange(plot, scaleKey, hardMin, hardMax, softMin, softMax);
const range = max - min; const range = max - min;
const valueStops = thresholds.steps.map( const valueStops = thresholds.steps.map(
(step) => (step) =>
@ -212,7 +237,7 @@ export function getScaleGradientFn(
} }
} else if (colorMode.getColors) { } else if (colorMode.getColors) {
const colors = colorMode.getColors(theme); const colors = colorMode.getColors(theme);
const [min, max] = getDataRange(plot, scaleKey); const [min, max] = getThresholdRange(plot, scaleKey, hardMin, hardMax, softMin, softMax);
const range = max - min; const range = max - min;
const valueStops = colors.map( const valueStops = colors.map(
(color, i) => (color, i) =>

View File

@ -17,7 +17,7 @@ export const plugin = new PanelPlugin<BarChartOptions, BarChartFieldConfig>(BarC
standardOptions: { standardOptions: {
[FieldConfigProperty.Color]: { [FieldConfigProperty.Color]: {
settings: { settings: {
byValueSupport: false, byValueSupport: true,
}, },
defaultValue: { defaultValue: {
mode: FieldColorModeId.PaletteClassic, mode: FieldColorModeId.PaletteClassic,

View File

@ -134,6 +134,10 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<BarChartOptions> = ({
show: !customConfig.hideFrom?.viz, show: !customConfig.hideFrom?.viz,
gradientMode: customConfig.gradientMode, gradientMode: customConfig.gradientMode,
thresholds: field.config.thresholds, thresholds: field.config.thresholds,
hardMin: field.config.min,
hardMax: field.config.max,
softMin: customConfig.axisSoftMin,
softMax: customConfig.axisSoftMax,
// The following properties are not used in the uPlot config, but are utilized as transport for legend config // The following properties are not used in the uPlot config, but are utilized as transport for legend config
dataFrameFieldIndex: { dataFrameFieldIndex: {

View File

@ -182,6 +182,11 @@ const prepConfig = (frame: DataFrame, theme: GrafanaTheme2) => {
gradientMode: customConfig.gradientMode, gradientMode: customConfig.gradientMode,
thresholds: field.config.thresholds, thresholds: field.config.thresholds,
hardMin: field.config.min,
hardMax: field.config.max,
softMin: customConfig.axisSoftMin,
softMax: customConfig.axisSoftMax,
// The following properties are not used in the uPlot config, but are utilized as transport for legend config // The following properties are not used in the uPlot config, but are utilized as transport for legend config
dataFrameFieldIndex: { dataFrameFieldIndex: {
fieldIndex: i, fieldIndex: i,

View File

@ -52,7 +52,7 @@ export const plugin = new PanelPlugin<PanelOptions, PanelFieldConfig>(HistogramP
standardOptions: { standardOptions: {
[FieldConfigProperty.Color]: { [FieldConfigProperty.Color]: {
settings: { settings: {
byValueSupport: false, byValueSupport: true,
}, },
defaultValue: { defaultValue: {
mode: FieldColorModeId.PaletteClassic, mode: FieldColorModeId.PaletteClassic,