Heatmap: Use dashboard timeRange for auto-sizing x buckets (#89382)

This commit is contained in:
Leon Sorokin 2024-06-18 21:51:20 -05:00 committed by GitHub
parent 7c69f3657b
commit 74bcfb284c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 63 additions and 21 deletions

View File

@ -15,6 +15,7 @@ import {
durationToMilliseconds,
parseDuration,
TransformationApplicabilityLevels,
TimeRange,
} from '@grafana/data';
import { isLikelyAscendingVector } from '@grafana/data/src/transformations/transformers/joinDataFrames';
import { config } from '@grafana/runtime';
@ -292,7 +293,14 @@ export function prepBucketFrames(frames: DataFrame[]): DataFrame[] {
}));
}
export function calculateHeatmapFromData(frames: DataFrame[], options: HeatmapCalculationOptions): DataFrame {
interface HeatmapCalculationOptionsWithTimeRange extends HeatmapCalculationOptions {
timeRange?: TimeRange;
}
export function calculateHeatmapFromData(
frames: DataFrame[],
options: HeatmapCalculationOptionsWithTimeRange
): DataFrame {
// Find fields in the heatmap
const { xField, yField, xs, ys } = findHeatmapFields(frames);
@ -329,6 +337,9 @@ export function calculateHeatmapFromData(frames: DataFrame[], options: HeatmapCa
ySize: yBucketsCfg.value ? +yBucketsCfg.value : undefined,
yLog:
scaleDistribution?.type === ScaleDistribution.Log ? (scaleDistribution?.log as 2 | 10 | undefined) : undefined,
xMin: options.timeRange?.from.valueOf(),
xMax: options.timeRange?.to.valueOf(),
});
const frame = {
@ -460,20 +471,23 @@ function heatmap(xs: number[], ys: number[], opts?: HeatmapOpts) {
let ySorted = opts?.ySorted ?? false;
// find x and y limits to pre-compute buckets struct
let minX = xSorted ? xs[0] : Infinity;
let minX = opts?.xMin ?? (xSorted ? xs[0] : Infinity);
let minY = ySorted ? ys[0] : Infinity;
let maxX = xSorted ? xs[len - 1] : -Infinity;
let maxX = opts?.xMax ?? (xSorted ? xs[len - 1] : -Infinity);
let maxY = ySorted ? ys[len - 1] : -Infinity;
let yExp = opts?.yLog;
let withPredefX = opts?.xMin != null && opts?.xMax != null;
let withPredefY = opts?.yMin != null && opts?.yMax != null;
for (let i = 0; i < len; i++) {
if (!xSorted) {
if (!xSorted && !withPredefX) {
minX = Math.min(minX, xs[i]);
maxX = Math.max(maxX, xs[i]);
}
if (!ySorted) {
if (!ySorted && !withPredefY) {
if (!yExp || ys[i] > 0) {
minY = Math.min(minY, ys[i]);
maxY = Math.max(maxY, ys[i]);
@ -500,7 +514,6 @@ function heatmap(xs: number[], ys: number[], opts?: HeatmapOpts) {
}
if (xMode === HeatmapCalculationMode.Count) {
// TODO: optionally use view range min/max instead of data range for bucket sizing
let approx = (maxX - minX) / Math.max(xBinIncr - 1, 1);
// nice-ify
let xIncrs = opts?.xTime ? niceTimeIncrs : niceLinearIncrs;
@ -509,7 +522,6 @@ function heatmap(xs: number[], ys: number[], opts?: HeatmapOpts) {
}
if (yMode === HeatmapCalculationMode.Count) {
// TODO: optionally use view range min/max instead of data range for bucket sizing
let approx = (maxY - minY) / Math.max(yBinIncr - 1, 1);
// nice-ify
let yIncrs = opts?.yTime ? niceTimeIncrs : niceLinearIncrs;

View File

@ -59,11 +59,19 @@ export const HeatmapPanel = ({
const info = useMemo(() => {
try {
return prepareHeatmapData(data.series, data.annotations, options, palette, theme, replaceVariables);
return prepareHeatmapData({
frames: data.series,
annotations: data.annotations,
options,
palette,
theme,
replaceVariables,
timeRange,
});
} catch (ex) {
return { warning: `${ex}` };
}
}, [data.series, data.annotations, options, palette, theme, replaceVariables]);
}, [data.series, data.annotations, options, palette, theme, replaceVariables, timeRange]);
const facets = useMemo(() => {
let exemplarsXFacet: number[] | undefined = []; // "Time" field

View File

@ -10,6 +10,7 @@ import {
GrafanaTheme2,
InterpolateFunction,
outerJoinDataFrames,
TimeRange,
ValueFormatter,
} from '@grafana/data';
import { parseSampleValue, sortSeriesByLabel } from '@grafana/prometheus';
@ -65,14 +66,25 @@ export interface HeatmapData {
warning?: string;
}
export function prepareHeatmapData(
frames: DataFrame[],
annotations: DataFrame[] | undefined,
options: Options,
palette: string[],
theme: GrafanaTheme2,
replaceVariables: InterpolateFunction = (v) => v
): HeatmapData {
interface PrepareHeatmapDataOptions {
frames: DataFrame[];
annotations?: DataFrame[];
options: Options;
palette: string[];
theme: GrafanaTheme2;
replaceVariables?: InterpolateFunction;
timeRange?: TimeRange;
}
export function prepareHeatmapData({
frames,
annotations,
options,
palette,
theme,
replaceVariables = (v) => v,
timeRange,
}: PrepareHeatmapDataOptions): HeatmapData {
if (!frames?.length) {
return {};
}
@ -104,7 +116,7 @@ export function prepareHeatmapData(
}
return getDenseHeatmapData(
calculateHeatmapFromData(frames, optionsCopy.calculation ?? {}),
calculateHeatmapFromData(frames, { ...options.calculation, timeRange }),
exemplars,
optionsCopy,
palette,
@ -113,7 +125,7 @@ export function prepareHeatmapData(
}
return getDenseHeatmapData(
calculateHeatmapFromData(frames, options.calculation ?? {}),
calculateHeatmapFromData(frames, { ...options.calculation, timeRange }),
exemplars,
options,
palette,

View File

@ -53,7 +53,12 @@ export const plugin = new PanelPlugin<Options, GraphFieldConfig>(HeatmapPanel)
// NOTE: this feels like overkill/expensive just to assert if we have an ordinal y
// can probably simplify without doing full dataprep
const palette = quantizeScheme(opts.color, config.theme2);
const v = prepareHeatmapData(context.data, undefined, opts, palette, config.theme2);
const v = prepareHeatmapData({
frames: context.data,
options: opts,
palette,
theme: config.theme2,
});
isOrdinalY = readHeatmapRowsCustomMeta(v.heatmap).yOrdinalDisplay != null;
} catch {}
}

View File

@ -20,7 +20,12 @@ export class HeatmapSuggestionsSupplier {
}
const palette = quantizeScheme(defaultOptions.color, config.theme2);
const info = prepareHeatmapData(builder.data.series, undefined, defaultOptions, palette, config.theme2);
const info = prepareHeatmapData({
frames: builder.data.series,
options: defaultOptions,
palette,
theme: config.theme2,
});
if (!info || info.warning) {
return;
}