* GraphNG: account for top canvas padding in gradient gen for color scheme/thresholds-by-value
* Updated test dashboard
* Added fix for issue when scaleMin was same as threshold
* fixed firefox issue
* revert docs changes
* update gdev dash for easier comparisons & regression spotting
* refactor
* optimize gradient re-gen/re-use and color more tinycolor.setAlpha() -> alpha(). update uPlot to dev build.
* fix percentage steps
* implement % threshold region rendering
* crisp threshold line rendering
* simplify
* WIP: hoverpoint dynamic color interpolation
* fix hover point color interp
* re-use gradient gen to draw threshold areas
* re-implement by-value color scales
* tweak comment
* mimic tinycolor behavior in colorManipulator.alpha() for empty colors
* explicitly disable hover points for BarChart and Histogram
* reduce test failures and required changes to tests
* fix barchart tests
* uPlot 1.6.15
Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
(cherry picked from commit 6a77cd43ae)
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -255,6 +255,10 @@ export function emphasize(color: string, coefficient = 0.15) {
|
|||||||
* @beta
|
* @beta
|
||||||
*/
|
*/
|
||||||
export function alpha(color: string, value: number) {
|
export function alpha(color: string, value: number) {
|
||||||
|
if (color === '') {
|
||||||
|
return '#000000';
|
||||||
|
}
|
||||||
|
|
||||||
value = clamp(value);
|
value = clamp(value);
|
||||||
|
|
||||||
// hex 6, hex 8 (w/alpha)
|
// hex 6, hex 8 (w/alpha)
|
||||||
|
|||||||
@@ -74,7 +74,7 @@
|
|||||||
"react-transition-group": "4.4.1",
|
"react-transition-group": "4.4.1",
|
||||||
"slate": "0.47.8",
|
"slate": "0.47.8",
|
||||||
"tinycolor2": "1.4.1",
|
"tinycolor2": "1.4.1",
|
||||||
"uplot": "1.6.14"
|
"uplot": "1.6.15"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-commonjs": "16.0.0",
|
"@rollup/plugin-commonjs": "16.0.0",
|
||||||
|
|||||||
@@ -436,7 +436,7 @@ describe('UPlotConfigBuilder', () => {
|
|||||||
theme: darkTheme,
|
theme: darkTheme,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(builder.getConfig().series[1].fill).toBe('rgba(255, 170, 187, 0.5)');
|
expect(builder.getConfig().series[1].fill).toBe('#FFAABB80');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('when fillColor is set ignore fillOpacity', () => {
|
it('when fillColor is set ignore fillOpacity', () => {
|
||||||
|
|||||||
@@ -25,10 +25,6 @@ const cursorDefaults: Cursor = {
|
|||||||
size: (u, seriesIdx) => u.series[seriesIdx].points.size * 2,
|
size: (u, seriesIdx) => u.series[seriesIdx].points.size * 2,
|
||||||
/*@ts-ignore*/
|
/*@ts-ignore*/
|
||||||
width: (u, seriesIdx, size) => size / 4,
|
width: (u, seriesIdx, size) => size / 4,
|
||||||
/*@ts-ignore*/
|
|
||||||
stroke: (u, seriesIdx) => u.series[seriesIdx].points.stroke(u, seriesIdx) + '80',
|
|
||||||
/*@ts-ignore*/
|
|
||||||
fill: (u, seriesIdx) => u.series[seriesIdx].points.stroke(u, seriesIdx),
|
|
||||||
},
|
},
|
||||||
focus: {
|
focus: {
|
||||||
prox: 30,
|
prox: 30,
|
||||||
@@ -50,6 +46,7 @@ export class UPlotConfigBuilder {
|
|||||||
private hooks: Hooks.Arrays = {};
|
private hooks: Hooks.Arrays = {};
|
||||||
private tz: string | undefined = undefined;
|
private tz: string | undefined = undefined;
|
||||||
private sync = false;
|
private sync = false;
|
||||||
|
private frame: DataFrame | undefined = undefined;
|
||||||
// to prevent more than one threshold per scale
|
// to prevent more than one threshold per scale
|
||||||
private thresholds: Record<string, UPlotThresholdOptions> = {};
|
private thresholds: Record<string, UPlotThresholdOptions> = {};
|
||||||
/**
|
/**
|
||||||
@@ -158,7 +155,10 @@ export class UPlotConfigBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setPrepData(prepData: PrepData) {
|
setPrepData(prepData: PrepData) {
|
||||||
this.prepData = prepData;
|
this.prepData = (frame) => {
|
||||||
|
this.frame = frame;
|
||||||
|
return prepData(frame);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
setSync() {
|
setSync() {
|
||||||
@@ -187,7 +187,25 @@ export class UPlotConfigBuilder {
|
|||||||
|
|
||||||
config.select = this.select;
|
config.select = this.select;
|
||||||
|
|
||||||
config.cursor = merge({}, cursorDefaults, this.cursor);
|
const pointColorFn = (alphaHex = '') => (u: uPlot, seriesIdx: number) => {
|
||||||
|
/*@ts-ignore*/
|
||||||
|
let s = u.series[seriesIdx].points._stroke;
|
||||||
|
|
||||||
|
// interpolate for gradients/thresholds
|
||||||
|
if (typeof s !== 'string') {
|
||||||
|
let field = this.frame!.fields[seriesIdx];
|
||||||
|
s = field.display!(field.values.get(u.cursor.idxs![seriesIdx]!)).color!;
|
||||||
|
}
|
||||||
|
|
||||||
|
return s + alphaHex;
|
||||||
|
};
|
||||||
|
|
||||||
|
config.cursor = merge({}, cursorDefaults, this.cursor, {
|
||||||
|
points: {
|
||||||
|
stroke: pointColorFn('80'),
|
||||||
|
fill: pointColorFn(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
config.tzDate = this.tzDate;
|
config.tzDate = this.tzDate;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { DataFrameFieldIndex, FALLBACK_COLOR, FieldColorMode, GrafanaTheme2, ThresholdsConfig } from '@grafana/data';
|
import {
|
||||||
import tinycolor from 'tinycolor2';
|
colorManipulator,
|
||||||
|
DataFrameFieldIndex,
|
||||||
|
FALLBACK_COLOR,
|
||||||
|
FieldColorMode,
|
||||||
|
GrafanaTheme2,
|
||||||
|
ThresholdsConfig,
|
||||||
|
} from '@grafana/data';
|
||||||
import uPlot, { Series } from 'uplot';
|
import uPlot, { Series } from 'uplot';
|
||||||
import {
|
import {
|
||||||
BarAlignment,
|
BarAlignment,
|
||||||
@@ -55,16 +61,18 @@ export class UPlotSeriesBuilder extends PlotConfigBuilder<SeriesProps, Series> {
|
|||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
let lineConfig: Partial<Series> = {};
|
let lineConfig: Partial<Series> = {};
|
||||||
const lineColor = this.getLineColor();
|
|
||||||
|
let lineColor = this.getLineColor();
|
||||||
|
|
||||||
|
// DrawStyle.Points mode also needs this for fill/stroke sharing & re-use in series.points. see getColor() below.
|
||||||
|
lineConfig.stroke = lineColor;
|
||||||
|
|
||||||
if (pathBuilder != null) {
|
if (pathBuilder != null) {
|
||||||
lineConfig.paths = pathBuilder;
|
lineConfig.paths = pathBuilder;
|
||||||
lineConfig.stroke = lineColor;
|
|
||||||
lineConfig.width = lineWidth;
|
lineConfig.width = lineWidth;
|
||||||
} else if (drawStyle === DrawStyle.Points) {
|
} else if (drawStyle === DrawStyle.Points) {
|
||||||
lineConfig.paths = () => null;
|
lineConfig.paths = () => null;
|
||||||
} else if (drawStyle != null) {
|
} else if (drawStyle != null) {
|
||||||
lineConfig.stroke = lineColor;
|
|
||||||
lineConfig.width = lineWidth;
|
lineConfig.width = lineWidth;
|
||||||
if (lineStyle && lineStyle.fill !== 'solid') {
|
if (lineStyle && lineStyle.fill !== 'solid') {
|
||||||
if (lineStyle.fill === 'dot') {
|
if (lineStyle.fill === 'dot') {
|
||||||
@@ -84,10 +92,14 @@ export class UPlotSeriesBuilder extends PlotConfigBuilder<SeriesProps, Series> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const useColor: uPlot.Series.Stroke =
|
||||||
|
// @ts-ignore
|
||||||
|
typeof lineColor === 'string' ? lineColor : (u, seriesIdx) => u.series[seriesIdx]._stroke;
|
||||||
|
|
||||||
const pointsConfig: Partial<Series> = {
|
const pointsConfig: Partial<Series> = {
|
||||||
points: {
|
points: {
|
||||||
stroke: lineColor,
|
stroke: useColor,
|
||||||
fill: lineColor,
|
fill: useColor,
|
||||||
size: pointSize,
|
size: pointSize,
|
||||||
filter: pointsFilter,
|
filter: pointsFilter,
|
||||||
},
|
},
|
||||||
@@ -153,7 +165,7 @@ export class UPlotSeriesBuilder extends PlotConfigBuilder<SeriesProps, Series> {
|
|||||||
return getScaleGradientFn(opacityPercent, theme, colorMode, thresholds);
|
return getScaleGradientFn(opacityPercent, theme, colorMode, thresholds);
|
||||||
default:
|
default:
|
||||||
if (opacityPercent > 0) {
|
if (opacityPercent > 0) {
|
||||||
return tinycolor(lineColor).setAlpha(opacityPercent).toString();
|
return colorManipulator.alpha(lineColor ?? '', opacityPercent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { getColorForTheme, GrafanaTheme2, ThresholdsConfig } from '@grafana/data';
|
import { GrafanaTheme2, ThresholdsConfig, ThresholdsMode } from '@grafana/data';
|
||||||
import tinycolor from 'tinycolor2';
|
import tinycolor from 'tinycolor2';
|
||||||
import { GraphThresholdsStyleConfig, GraphTresholdsStyleMode } from '../config';
|
import { GraphThresholdsStyleConfig, GraphTresholdsStyleMode } from '../config';
|
||||||
|
import { getDataRange, GradientDirection, scaleGradient } from './gradientFills';
|
||||||
|
|
||||||
export interface UPlotThresholdOptions {
|
export interface UPlotThresholdOptions {
|
||||||
scaleKey: string;
|
scaleKey: string;
|
||||||
@@ -13,7 +14,6 @@ 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 } = options;
|
||||||
const { steps } = thresholds;
|
|
||||||
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];
|
||||||
|
|
||||||
@@ -21,6 +21,18 @@ export function getThresholdsDrawHook(options: UPlotThresholdOptions) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let { steps, mode } = thresholds;
|
||||||
|
|
||||||
|
if (mode === ThresholdsMode.Percentage) {
|
||||||
|
let [min, max] = getDataRange(u, scaleKey);
|
||||||
|
let range = max - min;
|
||||||
|
|
||||||
|
steps = steps.map((step) => ({
|
||||||
|
...step,
|
||||||
|
value: min + range * (step.value / 100),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
function addLines() {
|
function addLines() {
|
||||||
// Thresholds below a transparent threshold is treated like "less than", and line drawn previous threshold
|
// Thresholds below a transparent threshold is treated like "less than", and line drawn previous threshold
|
||||||
let transparentIndex = 0;
|
let transparentIndex = 0;
|
||||||
@@ -40,9 +52,9 @@ export function getThresholdsDrawHook(options: UPlotThresholdOptions) {
|
|||||||
|
|
||||||
// if we are below a transparent index treat this a less then threshold, use previous thresholds color
|
// if we are below a transparent index treat this a less then threshold, use previous thresholds color
|
||||||
if (transparentIndex >= idx && idx > 0) {
|
if (transparentIndex >= idx && idx > 0) {
|
||||||
color = tinycolor(getColorForTheme(steps[idx - 1].color, theme.v1));
|
color = tinycolor(theme.visualization.getColorByName(steps[idx - 1].color));
|
||||||
} else {
|
} else {
|
||||||
color = tinycolor(getColorForTheme(step.color, theme.v1));
|
color = tinycolor(theme.visualization.getColorByName(step.color));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unless alpha specififed set to default value
|
// Unless alpha specififed set to default value
|
||||||
@@ -50,10 +62,10 @@ export function getThresholdsDrawHook(options: UPlotThresholdOptions) {
|
|||||||
color.setAlpha(0.7);
|
color.setAlpha(0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
let x0 = u.valToPos(xMin!, 'x', true);
|
let x0 = Math.round(u.valToPos(xMin!, 'x', true));
|
||||||
let y0 = u.valToPos(step.value, scaleKey, true);
|
let y0 = Math.round(u.valToPos(step.value, scaleKey, true));
|
||||||
let x1 = u.valToPos(xMax!, 'x', true);
|
let x1 = Math.round(u.valToPos(xMax!, 'x', true));
|
||||||
let y1 = u.valToPos(step.value, scaleKey, true);
|
let y1 = Math.round(u.valToPos(step.value, scaleKey, true));
|
||||||
|
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.lineWidth = 2;
|
ctx.lineWidth = 2;
|
||||||
@@ -66,49 +78,26 @@ export function getThresholdsDrawHook(options: UPlotThresholdOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addAreas() {
|
function addAreas() {
|
||||||
for (let idx = 0; idx < steps.length; idx++) {
|
let grd = scaleGradient(
|
||||||
const step = steps[idx];
|
u,
|
||||||
|
u.series[1].scale!,
|
||||||
|
GradientDirection.Up,
|
||||||
|
steps.map((step) => {
|
||||||
|
let color = tinycolor(theme.visualization.getColorByName(step.color));
|
||||||
|
|
||||||
// skip thresholds that cannot be seen
|
if (color.getAlpha() === 1) {
|
||||||
if (step.value > yMax!) {
|
color.setAlpha(0.15);
|
||||||
continue;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// if this is the last step make the next step the same color but +Infinity
|
return [step.value, color.toString()];
|
||||||
const nextStep =
|
}),
|
||||||
idx + 1 < steps.length
|
true
|
||||||
? steps[idx + 1]
|
);
|
||||||
: {
|
|
||||||
...step,
|
|
||||||
value: Infinity,
|
|
||||||
};
|
|
||||||
|
|
||||||
let color = tinycolor(getColorForTheme(step.color, theme.v1));
|
ctx.save();
|
||||||
|
ctx.fillStyle = grd;
|
||||||
// Ignore fully transparent colors
|
ctx.fillRect(u.bbox.left, u.bbox.top, u.bbox.width, u.bbox.height);
|
||||||
const alpha = color.getAlpha();
|
ctx.restore();
|
||||||
if (alpha === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// if no alpha set automatic alpha
|
|
||||||
if (alpha === 1) {
|
|
||||||
color = color.setAlpha(0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
let value = step.value === -Infinity ? yMin : step.value;
|
|
||||||
let nextValue = nextStep.value === Infinity || nextStep.value > yMax! ? yMax : nextStep.value;
|
|
||||||
|
|
||||||
let x0 = u.valToPos(xMin ?? 0, 'x', true);
|
|
||||||
let y0 = u.valToPos(value ?? 0, scaleKey, true);
|
|
||||||
let x1 = u.valToPos(xMax ?? 1, 'x', true);
|
|
||||||
let y1 = u.valToPos(nextValue ?? 1, scaleKey, true);
|
|
||||||
|
|
||||||
ctx.save();
|
|
||||||
ctx.fillStyle = color.toString();
|
|
||||||
ctx.fillRect(x0, y0, x1 - x0, y1 - y0);
|
|
||||||
ctx.restore();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (config.mode) {
|
switch (config.mode) {
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
import { FieldColorMode, FieldColorModeId, GrafanaTheme2, ThresholdsConfig, ThresholdsMode } from '@grafana/data';
|
import {
|
||||||
|
colorManipulator,
|
||||||
|
FieldColorMode,
|
||||||
|
FieldColorModeId,
|
||||||
|
GrafanaTheme2,
|
||||||
|
ThresholdsConfig,
|
||||||
|
ThresholdsMode,
|
||||||
|
} from '@grafana/data';
|
||||||
import tinycolor from 'tinycolor2';
|
import tinycolor from 'tinycolor2';
|
||||||
import uPlot from 'uplot';
|
import uPlot from 'uplot';
|
||||||
import { getCanvasContext } from '../../../utils/measureText';
|
import { getCanvasContext } from '../../../utils/measureText';
|
||||||
@@ -11,8 +18,8 @@ export function getOpacityGradientFn(
|
|||||||
const ctx = getCanvasContext();
|
const ctx = getCanvasContext();
|
||||||
const gradient = ctx.createLinearGradient(0, plot.bbox.top, 0, plot.bbox.top + plot.bbox.height);
|
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(0, colorManipulator.alpha(color, opacity));
|
||||||
gradient.addColorStop(1, tinycolor(color).setAlpha(0).toRgbString());
|
gradient.addColorStop(1, colorManipulator.alpha(color, 0));
|
||||||
|
|
||||||
return gradient;
|
return gradient;
|
||||||
};
|
};
|
||||||
@@ -39,10 +46,132 @@ export function getHueGradientFn(
|
|||||||
return gradient;
|
return gradient;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Experimental & quick and dirty test
|
export enum GradientDirection {
|
||||||
* Not being used
|
'Right' = 0,
|
||||||
*/
|
'Up' = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
type ValueStop = [value: number, color: string];
|
||||||
|
|
||||||
|
type ScaleValueStops = ValueStop[];
|
||||||
|
|
||||||
|
export function scaleGradient(
|
||||||
|
u: uPlot,
|
||||||
|
scaleKey: string,
|
||||||
|
dir: GradientDirection,
|
||||||
|
scaleStops: ScaleValueStops,
|
||||||
|
discrete = false
|
||||||
|
) {
|
||||||
|
let scale = u.scales[scaleKey];
|
||||||
|
|
||||||
|
// we want the stop below or at the scaleMax
|
||||||
|
// and the stop below or at the scaleMin, else the stop above scaleMin
|
||||||
|
let minStopIdx: number | null = null;
|
||||||
|
let maxStopIdx: number | null = null;
|
||||||
|
|
||||||
|
for (let i = 0; i < scaleStops.length; i++) {
|
||||||
|
let stopVal = scaleStops[i][0];
|
||||||
|
|
||||||
|
if (stopVal <= scale.min! || minStopIdx == null) {
|
||||||
|
minStopIdx = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
maxStopIdx = i;
|
||||||
|
|
||||||
|
if (stopVal >= scale.max!) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minStopIdx === maxStopIdx) {
|
||||||
|
return scaleStops[minStopIdx!][1];
|
||||||
|
}
|
||||||
|
|
||||||
|
let minStopVal = scaleStops[minStopIdx!][0];
|
||||||
|
let maxStopVal = scaleStops[maxStopIdx!][0];
|
||||||
|
|
||||||
|
if (minStopVal === -Infinity) {
|
||||||
|
minStopVal = scale.min!;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxStopVal === Infinity) {
|
||||||
|
maxStopVal = scale.max!;
|
||||||
|
}
|
||||||
|
|
||||||
|
let minStopPos = Math.round(u.valToPos(minStopVal, scaleKey, true));
|
||||||
|
let maxStopPos = Math.round(u.valToPos(maxStopVal, scaleKey, true));
|
||||||
|
|
||||||
|
let range = minStopPos - maxStopPos;
|
||||||
|
|
||||||
|
let x0, y0, x1, y1;
|
||||||
|
|
||||||
|
if (dir === GradientDirection.Up) {
|
||||||
|
x0 = x1 = 0;
|
||||||
|
y0 = minStopPos;
|
||||||
|
y1 = maxStopPos;
|
||||||
|
} else {
|
||||||
|
y0 = y1 = 0;
|
||||||
|
x0 = minStopPos;
|
||||||
|
x1 = maxStopPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ctx = getCanvasContext();
|
||||||
|
|
||||||
|
let grd = ctx.createLinearGradient(x0, y0, x1, y1);
|
||||||
|
|
||||||
|
let prevColor: string;
|
||||||
|
|
||||||
|
for (let i = minStopIdx!; i <= maxStopIdx!; i++) {
|
||||||
|
let s = scaleStops[i];
|
||||||
|
|
||||||
|
let stopPos =
|
||||||
|
i === minStopIdx ? minStopPos : i === maxStopIdx ? maxStopPos : Math.round(u.valToPos(s[0], scaleKey, true));
|
||||||
|
|
||||||
|
let pct = (minStopPos - stopPos) / range;
|
||||||
|
|
||||||
|
if (discrete && i > minStopIdx!) {
|
||||||
|
grd.addColorStop(pct, prevColor!);
|
||||||
|
}
|
||||||
|
|
||||||
|
grd.addColorStop(pct, (prevColor = s[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return grd;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDataRange(plot: uPlot, scaleKey: string) {
|
||||||
|
let sc = plot.scales[scaleKey];
|
||||||
|
|
||||||
|
let min = Infinity;
|
||||||
|
let max = -Infinity;
|
||||||
|
|
||||||
|
plot.series.forEach((ser, seriesIdx) => {
|
||||||
|
if (ser.show && ser.scale === scaleKey) {
|
||||||
|
// uPlot skips finding data min/max when a scale has a pre-defined range
|
||||||
|
if (ser.min == null) {
|
||||||
|
let data = plot.data[seriesIdx];
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
if (data[i] != null) {
|
||||||
|
min = Math.min(min, data[i]!);
|
||||||
|
max = Math.max(max, data[i]!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
min = Math.min(min, ser.min!);
|
||||||
|
max = Math.max(max, ser.max!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (max === min) {
|
||||||
|
min = sc.min!;
|
||||||
|
max = sc.max!;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [min, max];
|
||||||
|
}
|
||||||
|
|
||||||
export function getScaleGradientFn(
|
export function getScaleGradientFn(
|
||||||
opacity: number,
|
opacity: number,
|
||||||
theme: GrafanaTheme2,
|
theme: GrafanaTheme2,
|
||||||
@@ -58,68 +187,41 @@ export function getScaleGradientFn(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (plot: uPlot, seriesIdx: number) => {
|
return (plot: uPlot, seriesIdx: number) => {
|
||||||
// A uplot bug (I think) where this is called before there is bbox
|
let scaleKey = plot.series[seriesIdx].scale!;
|
||||||
// Color used for cursor highlight, not sure what to do here as this is called before we have bbox
|
|
||||||
// and only once so same color is used for all points
|
|
||||||
if (plot.bbox.top == null) {
|
|
||||||
return theme.colors.text.primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ctx = getCanvasContext();
|
let gradient: CanvasGradient | string = '';
|
||||||
const gradient = ctx.createLinearGradient(0, plot.bbox.top, 0, plot.bbox.top + plot.bbox.height);
|
|
||||||
const canvasHeight = plot.bbox.height;
|
|
||||||
const series = plot.series[seriesIdx];
|
|
||||||
const scale = plot.scales[series.scale!];
|
|
||||||
const scaleMin = scale.min ?? 0;
|
|
||||||
const scaleMax = scale.max ?? 100;
|
|
||||||
const scaleRange = scaleMax - scaleMin;
|
|
||||||
|
|
||||||
const addColorStop = (value: number, color: string) => {
|
|
||||||
const pos = plot.valToPos(value, series.scale!, true);
|
|
||||||
// when above range we get negative values here
|
|
||||||
if (pos < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const percent = Math.max(pos / canvasHeight, 0);
|
|
||||||
const realColor = tinycolor(theme.visualization.getColorByName(color)).setAlpha(opacity).toString();
|
|
||||||
const colorStopPos = Math.min(percent, 1);
|
|
||||||
|
|
||||||
gradient.addColorStop(colorStopPos, realColor);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (colorMode.id === FieldColorModeId.Thresholds) {
|
if (colorMode.id === FieldColorModeId.Thresholds) {
|
||||||
for (let idx = 0; idx < thresholds.steps.length; idx++) {
|
if (thresholds.mode === ThresholdsMode.Absolute) {
|
||||||
const step = thresholds.steps[idx];
|
const valueStops = thresholds.steps.map(
|
||||||
|
(step) =>
|
||||||
if (thresholds.mode === ThresholdsMode.Absolute) {
|
[step.value, colorManipulator.alpha(theme.visualization.getColorByName(step.color), opacity)] as ValueStop
|
||||||
const value = step.value === -Infinity ? scaleMin : step.value;
|
);
|
||||||
addColorStop(value, step.color);
|
gradient = scaleGradient(plot, scaleKey, GradientDirection.Up, valueStops, true);
|
||||||
|
} else {
|
||||||
if (thresholds.steps.length > idx + 1) {
|
const [min, max] = getDataRange(plot, scaleKey);
|
||||||
// to make the gradient discrete
|
const range = max - min;
|
||||||
addColorStop(thresholds.steps[idx + 1].value - 0.00000001, step.color);
|
const valueStops = thresholds.steps.map(
|
||||||
}
|
(step) =>
|
||||||
} else {
|
[
|
||||||
const percent = step.value === -Infinity ? 0 : step.value;
|
min + range * (step.value / 100),
|
||||||
const realValue = (percent / 100) * scaleRange;
|
colorManipulator.alpha(theme.visualization.getColorByName(step.color), opacity),
|
||||||
addColorStop(realValue, step.color);
|
] as ValueStop
|
||||||
|
);
|
||||||
// to make the gradient discrete
|
gradient = scaleGradient(plot, scaleKey, GradientDirection.Up, valueStops, true);
|
||||||
if (thresholds.steps.length > idx + 1) {
|
|
||||||
// to make the gradient discrete
|
|
||||||
const nextValue = (thresholds.steps[idx + 1].value / 100) * scaleRange - 0.0000001;
|
|
||||||
addColorStop(nextValue, step.color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (colorMode.getColors) {
|
} else if (colorMode.getColors) {
|
||||||
const colors = colorMode.getColors(theme);
|
const colors = colorMode.getColors(theme);
|
||||||
const stepValue = (scaleMax - scaleMin) / colors.length;
|
const [min, max] = getDataRange(plot, scaleKey);
|
||||||
|
const range = max - min;
|
||||||
for (let idx = 0; idx < colors.length; idx++) {
|
const valueStops = colors.map(
|
||||||
addColorStop(scaleMin + stepValue * idx, colors[idx]);
|
(color, i) =>
|
||||||
}
|
[
|
||||||
|
min + range * (i / (colors.length - 1)),
|
||||||
|
colorManipulator.alpha(theme.visualization.getColorByName(color), opacity),
|
||||||
|
] as ValueStop
|
||||||
|
);
|
||||||
|
gradient = scaleGradient(plot, scaleKey, GradientDirection.Up, valueStops, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return gradient;
|
return gradient;
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ Object {
|
|||||||
},
|
},
|
||||||
"points": Object {
|
"points": Object {
|
||||||
"fill": [Function],
|
"fill": [Function],
|
||||||
|
"show": false,
|
||||||
"size": [Function],
|
"size": [Function],
|
||||||
"stroke": [Function],
|
"stroke": [Function],
|
||||||
"width": [Function],
|
"width": [Function],
|
||||||
@@ -191,6 +192,7 @@ Object {
|
|||||||
},
|
},
|
||||||
"points": Object {
|
"points": Object {
|
||||||
"fill": [Function],
|
"fill": [Function],
|
||||||
|
"show": false,
|
||||||
"size": [Function],
|
"size": [Function],
|
||||||
"stroke": [Function],
|
"stroke": [Function],
|
||||||
"width": [Function],
|
"width": [Function],
|
||||||
@@ -319,6 +321,7 @@ Object {
|
|||||||
},
|
},
|
||||||
"points": Object {
|
"points": Object {
|
||||||
"fill": [Function],
|
"fill": [Function],
|
||||||
|
"show": false,
|
||||||
"size": [Function],
|
"size": [Function],
|
||||||
"stroke": [Function],
|
"stroke": [Function],
|
||||||
"width": [Function],
|
"width": [Function],
|
||||||
@@ -447,6 +450,7 @@ Object {
|
|||||||
},
|
},
|
||||||
"points": Object {
|
"points": Object {
|
||||||
"fill": [Function],
|
"fill": [Function],
|
||||||
|
"show": false,
|
||||||
"size": [Function],
|
"size": [Function],
|
||||||
"stroke": [Function],
|
"stroke": [Function],
|
||||||
"width": [Function],
|
"width": [Function],
|
||||||
@@ -575,6 +579,7 @@ Object {
|
|||||||
},
|
},
|
||||||
"points": Object {
|
"points": Object {
|
||||||
"fill": [Function],
|
"fill": [Function],
|
||||||
|
"show": false,
|
||||||
"size": [Function],
|
"size": [Function],
|
||||||
"stroke": [Function],
|
"stroke": [Function],
|
||||||
"width": [Function],
|
"width": [Function],
|
||||||
@@ -703,6 +708,7 @@ Object {
|
|||||||
},
|
},
|
||||||
"points": Object {
|
"points": Object {
|
||||||
"fill": [Function],
|
"fill": [Function],
|
||||||
|
"show": false,
|
||||||
"size": [Function],
|
"size": [Function],
|
||||||
"stroke": [Function],
|
"stroke": [Function],
|
||||||
"width": [Function],
|
"width": [Function],
|
||||||
@@ -831,6 +837,7 @@ Object {
|
|||||||
},
|
},
|
||||||
"points": Object {
|
"points": Object {
|
||||||
"fill": [Function],
|
"fill": [Function],
|
||||||
|
"show": false,
|
||||||
"size": [Function],
|
"size": [Function],
|
||||||
"stroke": [Function],
|
"stroke": [Function],
|
||||||
"width": [Function],
|
"width": [Function],
|
||||||
@@ -959,6 +966,7 @@ Object {
|
|||||||
},
|
},
|
||||||
"points": Object {
|
"points": Object {
|
||||||
"fill": [Function],
|
"fill": [Function],
|
||||||
|
"show": false,
|
||||||
"size": [Function],
|
"size": [Function],
|
||||||
"stroke": [Function],
|
"stroke": [Function],
|
||||||
"width": [Function],
|
"width": [Function],
|
||||||
|
|||||||
@@ -324,6 +324,7 @@ export function getConfig(opts: BarsOptions, theme: GrafanaTheme2) {
|
|||||||
cursor: {
|
cursor: {
|
||||||
x: false,
|
x: false,
|
||||||
y: false,
|
y: false,
|
||||||
|
points: { show: false },
|
||||||
},
|
},
|
||||||
// scale & axis opts
|
// scale & axis opts
|
||||||
xValues,
|
xValues,
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ const prepConfig = (frame: DataFrame, theme: GrafanaTheme2) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
builder.setCursor({
|
builder.setCursor({
|
||||||
|
points: { show: false },
|
||||||
drag: {
|
drag: {
|
||||||
x: true,
|
x: true,
|
||||||
y: false,
|
y: false,
|
||||||
|
|||||||
@@ -23401,10 +23401,10 @@ update-notifier@^2.5.0:
|
|||||||
semver-diff "^2.0.0"
|
semver-diff "^2.0.0"
|
||||||
xdg-basedir "^3.0.0"
|
xdg-basedir "^3.0.0"
|
||||||
|
|
||||||
uplot@1.6.14:
|
uplot@1.6.15:
|
||||||
version "1.6.14"
|
version "1.6.15"
|
||||||
resolved "https://registry.yarnpkg.com/uplot/-/uplot-1.6.14.tgz#49edfaea3090a9c71d8ae389780b90635aeda3e0"
|
resolved "https://registry.yarnpkg.com/uplot/-/uplot-1.6.15.tgz#785ca9f66d9c28ec2fdfb7e623d627ea0dcb0dd5"
|
||||||
integrity sha512-I/fO/pujHe6uurtCEVy6L0Vy6/p7AclbrUGu3Mw+oW0PTGPo0khnAWLyyDqSRyMyOwIin8y5HbBEiN3g4qOLuw==
|
integrity sha512-6Fgq9tMaEM9Yu9oLkKd0w7VLJtV8LHG6dBrg1TmYi0LmSLkrj2Hqr11IrHk68cMaExnWAqay6YToQCrMZt1fcQ==
|
||||||
|
|
||||||
upper-case@^1.1.1:
|
upper-case@^1.1.1:
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
|
|||||||
Reference in New Issue
Block a user