mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Heatmap: Use dashboard timeRange for auto-sizing x buckets (#89382)
This commit is contained in:
parent
7c69f3657b
commit
74bcfb284c
@ -15,6 +15,7 @@ import {
|
|||||||
durationToMilliseconds,
|
durationToMilliseconds,
|
||||||
parseDuration,
|
parseDuration,
|
||||||
TransformationApplicabilityLevels,
|
TransformationApplicabilityLevels,
|
||||||
|
TimeRange,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { isLikelyAscendingVector } from '@grafana/data/src/transformations/transformers/joinDataFrames';
|
import { isLikelyAscendingVector } from '@grafana/data/src/transformations/transformers/joinDataFrames';
|
||||||
import { config } from '@grafana/runtime';
|
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
|
// Find fields in the heatmap
|
||||||
const { xField, yField, xs, ys } = findHeatmapFields(frames);
|
const { xField, yField, xs, ys } = findHeatmapFields(frames);
|
||||||
|
|
||||||
@ -329,6 +337,9 @@ export function calculateHeatmapFromData(frames: DataFrame[], options: HeatmapCa
|
|||||||
ySize: yBucketsCfg.value ? +yBucketsCfg.value : undefined,
|
ySize: yBucketsCfg.value ? +yBucketsCfg.value : undefined,
|
||||||
yLog:
|
yLog:
|
||||||
scaleDistribution?.type === ScaleDistribution.Log ? (scaleDistribution?.log as 2 | 10 | undefined) : undefined,
|
scaleDistribution?.type === ScaleDistribution.Log ? (scaleDistribution?.log as 2 | 10 | undefined) : undefined,
|
||||||
|
|
||||||
|
xMin: options.timeRange?.from.valueOf(),
|
||||||
|
xMax: options.timeRange?.to.valueOf(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const frame = {
|
const frame = {
|
||||||
@ -460,20 +471,23 @@ function heatmap(xs: number[], ys: number[], opts?: HeatmapOpts) {
|
|||||||
let ySorted = opts?.ySorted ?? false;
|
let ySorted = opts?.ySorted ?? false;
|
||||||
|
|
||||||
// find x and y limits to pre-compute buckets struct
|
// 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 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 maxY = ySorted ? ys[len - 1] : -Infinity;
|
||||||
|
|
||||||
let yExp = opts?.yLog;
|
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++) {
|
for (let i = 0; i < len; i++) {
|
||||||
if (!xSorted) {
|
if (!xSorted && !withPredefX) {
|
||||||
minX = Math.min(minX, xs[i]);
|
minX = Math.min(minX, xs[i]);
|
||||||
maxX = Math.max(maxX, xs[i]);
|
maxX = Math.max(maxX, xs[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ySorted) {
|
if (!ySorted && !withPredefY) {
|
||||||
if (!yExp || ys[i] > 0) {
|
if (!yExp || ys[i] > 0) {
|
||||||
minY = Math.min(minY, ys[i]);
|
minY = Math.min(minY, ys[i]);
|
||||||
maxY = Math.max(maxY, ys[i]);
|
maxY = Math.max(maxY, ys[i]);
|
||||||
@ -500,7 +514,6 @@ function heatmap(xs: number[], ys: number[], opts?: HeatmapOpts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (xMode === HeatmapCalculationMode.Count) {
|
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);
|
let approx = (maxX - minX) / Math.max(xBinIncr - 1, 1);
|
||||||
// nice-ify
|
// nice-ify
|
||||||
let xIncrs = opts?.xTime ? niceTimeIncrs : niceLinearIncrs;
|
let xIncrs = opts?.xTime ? niceTimeIncrs : niceLinearIncrs;
|
||||||
@ -509,7 +522,6 @@ function heatmap(xs: number[], ys: number[], opts?: HeatmapOpts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (yMode === HeatmapCalculationMode.Count) {
|
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);
|
let approx = (maxY - minY) / Math.max(yBinIncr - 1, 1);
|
||||||
// nice-ify
|
// nice-ify
|
||||||
let yIncrs = opts?.yTime ? niceTimeIncrs : niceLinearIncrs;
|
let yIncrs = opts?.yTime ? niceTimeIncrs : niceLinearIncrs;
|
||||||
|
@ -59,11 +59,19 @@ export const HeatmapPanel = ({
|
|||||||
|
|
||||||
const info = useMemo(() => {
|
const info = useMemo(() => {
|
||||||
try {
|
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) {
|
} catch (ex) {
|
||||||
return { warning: `${ex}` };
|
return { warning: `${ex}` };
|
||||||
}
|
}
|
||||||
}, [data.series, data.annotations, options, palette, theme, replaceVariables]);
|
}, [data.series, data.annotations, options, palette, theme, replaceVariables, timeRange]);
|
||||||
|
|
||||||
const facets = useMemo(() => {
|
const facets = useMemo(() => {
|
||||||
let exemplarsXFacet: number[] | undefined = []; // "Time" field
|
let exemplarsXFacet: number[] | undefined = []; // "Time" field
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
GrafanaTheme2,
|
GrafanaTheme2,
|
||||||
InterpolateFunction,
|
InterpolateFunction,
|
||||||
outerJoinDataFrames,
|
outerJoinDataFrames,
|
||||||
|
TimeRange,
|
||||||
ValueFormatter,
|
ValueFormatter,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { parseSampleValue, sortSeriesByLabel } from '@grafana/prometheus';
|
import { parseSampleValue, sortSeriesByLabel } from '@grafana/prometheus';
|
||||||
@ -65,14 +66,25 @@ export interface HeatmapData {
|
|||||||
warning?: string;
|
warning?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function prepareHeatmapData(
|
interface PrepareHeatmapDataOptions {
|
||||||
frames: DataFrame[],
|
frames: DataFrame[];
|
||||||
annotations: DataFrame[] | undefined,
|
annotations?: DataFrame[];
|
||||||
options: Options,
|
options: Options;
|
||||||
palette: string[],
|
palette: string[];
|
||||||
theme: GrafanaTheme2,
|
theme: GrafanaTheme2;
|
||||||
replaceVariables: InterpolateFunction = (v) => v
|
replaceVariables?: InterpolateFunction;
|
||||||
): HeatmapData {
|
timeRange?: TimeRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prepareHeatmapData({
|
||||||
|
frames,
|
||||||
|
annotations,
|
||||||
|
options,
|
||||||
|
palette,
|
||||||
|
theme,
|
||||||
|
replaceVariables = (v) => v,
|
||||||
|
timeRange,
|
||||||
|
}: PrepareHeatmapDataOptions): HeatmapData {
|
||||||
if (!frames?.length) {
|
if (!frames?.length) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -104,7 +116,7 @@ export function prepareHeatmapData(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return getDenseHeatmapData(
|
return getDenseHeatmapData(
|
||||||
calculateHeatmapFromData(frames, optionsCopy.calculation ?? {}),
|
calculateHeatmapFromData(frames, { ...options.calculation, timeRange }),
|
||||||
exemplars,
|
exemplars,
|
||||||
optionsCopy,
|
optionsCopy,
|
||||||
palette,
|
palette,
|
||||||
@ -113,7 +125,7 @@ export function prepareHeatmapData(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return getDenseHeatmapData(
|
return getDenseHeatmapData(
|
||||||
calculateHeatmapFromData(frames, options.calculation ?? {}),
|
calculateHeatmapFromData(frames, { ...options.calculation, timeRange }),
|
||||||
exemplars,
|
exemplars,
|
||||||
options,
|
options,
|
||||||
palette,
|
palette,
|
||||||
|
@ -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
|
// NOTE: this feels like overkill/expensive just to assert if we have an ordinal y
|
||||||
// can probably simplify without doing full dataprep
|
// can probably simplify without doing full dataprep
|
||||||
const palette = quantizeScheme(opts.color, config.theme2);
|
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;
|
isOrdinalY = readHeatmapRowsCustomMeta(v.heatmap).yOrdinalDisplay != null;
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,12 @@ export class HeatmapSuggestionsSupplier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const palette = quantizeScheme(defaultOptions.color, config.theme2);
|
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) {
|
if (!info || info.warning) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user