Sparkline: Improve min/max logic to avoid issues for very narrow deltas (#115030)
* Sparkline: Prevent infinite loop when rendering a sparkline with a single value * some tests for this case * refactor out utils, experiment with getting highlightIndex working * add comments throughout for #112977 * remove unused import * Update Sparkline.test.tsx * fix points mode rendering * Sparkline: Improve min/max logic to avoid issues for very narrow deltas * spread all config * defaults deep * delete unused import * remove go.work.sum delta * line break at end of file
This commit is contained in:
@@ -114,6 +114,13 @@ describe('Get y range', () => {
|
||||
config: {},
|
||||
state: { range: { min: -2, max: -2, delta: 0 } },
|
||||
};
|
||||
const decimalsCloseYField: Field = {
|
||||
name: 'y',
|
||||
values: [2, 1.999999999999999, 2.000000000000001, 2, 2],
|
||||
type: FieldType.number,
|
||||
config: {},
|
||||
state: { range: { min: 1.999999999999999, max: 2.000000000000001, delta: 0 } },
|
||||
};
|
||||
const xField: Field = {
|
||||
name: 'x',
|
||||
values: [1000, 2000, 3000, 4000, 5000],
|
||||
@@ -154,12 +161,12 @@ describe('Get y range', () => {
|
||||
{
|
||||
description: 'straight line',
|
||||
field: straightLineYField,
|
||||
expected: [0, 4],
|
||||
expected: [2, 4],
|
||||
},
|
||||
{
|
||||
description: 'straight line, negative values',
|
||||
field: straightLineNegYField,
|
||||
expected: [-4, 0],
|
||||
expected: [-4, -2],
|
||||
},
|
||||
{
|
||||
description: 'straight line with config min and max',
|
||||
@@ -171,8 +178,13 @@ describe('Get y range', () => {
|
||||
field: { ...straightLineYField, config: { noValue: '0' } },
|
||||
expected: [0, 2],
|
||||
},
|
||||
{
|
||||
description: 'long decimals which are nearly equal and result in a functional delta of 0',
|
||||
field: decimalsCloseYField,
|
||||
expected: [2, 4],
|
||||
},
|
||||
])(`should return correct range for $description`, ({ field, expected }) => {
|
||||
const actual = getYRange(field, getAlignedFrame(field));
|
||||
const actual = getYRange(getAlignedFrame(field));
|
||||
expect(actual).toEqual(expected);
|
||||
expect(actual[0]).toBeLessThan(actual[1]!);
|
||||
});
|
||||
|
||||
@@ -3,7 +3,6 @@ import { Range } from 'uplot';
|
||||
import {
|
||||
applyNullInsertThreshold,
|
||||
DataFrame,
|
||||
Field,
|
||||
FieldConfig,
|
||||
FieldSparkline,
|
||||
FieldType,
|
||||
@@ -11,6 +10,7 @@ import {
|
||||
GrafanaTheme2,
|
||||
isLikelyAscendingVector,
|
||||
nullToValue,
|
||||
roundDecimals,
|
||||
sortDataFrame,
|
||||
} from '@grafana/data';
|
||||
import { t } from '@grafana/i18n';
|
||||
@@ -68,35 +68,49 @@ export function preparePlotFrame(sparkline: FieldSparkline, config?: FieldConfig
|
||||
/**
|
||||
* apply configuration defaults and ensure that the range is never two equal values.
|
||||
*/
|
||||
export function getYRange(field: Field, alignedFrame: DataFrame): Range.MinMax {
|
||||
let { min, max } = alignedFrame.fields[1].state?.range!;
|
||||
export function getYRange(alignedFrame: DataFrame): Range.MinMax {
|
||||
const field = alignedFrame.fields[1];
|
||||
let { min, max } = field.state?.range!;
|
||||
|
||||
// enure that the min/max from the field config are respected
|
||||
min = Math.max(min!, field.config.min ?? -Infinity);
|
||||
max = Math.min(max!, field.config.max ?? Infinity);
|
||||
// enure that the min/max from the field config are respected.
|
||||
min = Math.min(min!, field.config.min ?? Infinity);
|
||||
max = Math.max(max!, field.config.max ?? -Infinity);
|
||||
|
||||
// console.log({ min, max });
|
||||
|
||||
// if noValue is set, ensure that it is included in the range as well
|
||||
const noValue = +alignedFrame.fields[1].config?.noValue!;
|
||||
const noValue = +field.config?.noValue!;
|
||||
if (!Number.isNaN(noValue)) {
|
||||
min = Math.min(min, noValue);
|
||||
max = Math.max(max, noValue);
|
||||
}
|
||||
|
||||
// if min and max are equal after all of that, create a range
|
||||
// that allows the sparkline to be visible in the center of the viz
|
||||
if (min === max) {
|
||||
if (min === 0) {
|
||||
max = 100;
|
||||
} else if (min < 0) {
|
||||
max = 0;
|
||||
min *= 2;
|
||||
} else {
|
||||
min = 0;
|
||||
max *= 2;
|
||||
}
|
||||
// call roundDecimals to mirror what is going to eventually happen in uplot
|
||||
let roundedMin = roundDecimals(min, field.config.decimals ?? 0);
|
||||
let roundedMax = roundDecimals(max, field.config.decimals ?? 0);
|
||||
|
||||
// if the rounded min and max are different,
|
||||
// we can return the real min and max.
|
||||
if (roundedMin !== roundedMax) {
|
||||
return [min, max];
|
||||
}
|
||||
|
||||
return [min, max];
|
||||
// we are forced to tweak the min and max since they
|
||||
// will be treated as equal after rounding by uPlot.
|
||||
if (roundedMin === 0) {
|
||||
// both are zero
|
||||
roundedMax = 1;
|
||||
} else if (roundedMin < 0) {
|
||||
// both are negative
|
||||
// max = 0;
|
||||
roundedMin *= 2;
|
||||
} else {
|
||||
// both are positive
|
||||
// min = 0;
|
||||
roundedMax *= 2;
|
||||
}
|
||||
|
||||
return [roundedMin, roundedMax];
|
||||
}
|
||||
|
||||
// TODO: #112977 enable highlight index
|
||||
@@ -182,7 +196,7 @@ export const prepareConfig = (
|
||||
scaleKey,
|
||||
orientation: ScaleOrientation.Vertical,
|
||||
direction: ScaleDirection.Up,
|
||||
range: () => getYRange(field, dataFrame),
|
||||
range: () => getYRange(dataFrame),
|
||||
});
|
||||
|
||||
builder.addAxis({
|
||||
|
||||
Reference in New Issue
Block a user