Heatmap: consolidate naming conventions (#50272)

This commit is contained in:
Ryan McKinley 2022-06-13 08:08:33 -07:00 committed by GitHub
parent d8d3a9c561
commit 1902357693
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 78 additions and 84 deletions

View File

@ -13,17 +13,19 @@ export enum DataFrameType {
DirectoryListing = 'directory-listing',
/**
* First field is X, the rest are bucket values
* First field is X, the rest are ordinal values used as rows in the heatmap
*/
HeatmapBuckets = 'heatmap-buckets',
HeatmapRows = 'heatmap-rows',
/**
* Explicit fields for:
* xMin, yMin, count, ...
*
* All values in the grid exist and have regular spacing
*
* If the y value is actually ordinal, use `meta.custom` to specify the bucket lookup values
*/
HeatmapScanlines = 'heatmap-scanlines',
HeatmapCells = 'heatmap-cells',
/**
* WIP sparse heatmap support

View File

@ -1,7 +1,7 @@
import { FieldType } from '@grafana/data';
import { toDataFrame } from '@grafana/data/src/dataframe/processDataFrame';
import { bucketsToScanlines, calculateHeatmapFromData } from './heatmap';
import { rowsToCellsHeatmap, calculateHeatmapFromData } from './heatmap';
import { HeatmapCalculationOptions } from './models.gen';
describe('Heatmap transformer', () => {
@ -58,7 +58,7 @@ describe('Heatmap transformer', () => {
],
});
const heatmap = bucketsToScanlines({ frame, value: 'Speed' });
const heatmap = rowsToCellsHeatmap({ frame, value: 'Speed' });
expect(heatmap.fields.map((f) => ({ name: f.name, type: f.type, config: f.config }))).toMatchInlineSnapshot(`
Array [
Object {
@ -92,7 +92,7 @@ describe('Heatmap transformer', () => {
"C",
],
},
"type": "heatmap-scanlines",
"type": "heatmap-cells",
}
`);
expect(heatmap.fields[1].values.toArray()).toMatchInlineSnapshot(`

View File

@ -14,7 +14,7 @@ import {
} from '@grafana/data';
import { ScaleDistribution } from '@grafana/schema';
import { HeatmapBucketLayout, HeatmapCalculationMode, HeatmapCalculationOptions } from './models.gen';
import { HeatmapCellLayout, HeatmapCalculationMode, HeatmapCalculationOptions } from './models.gen';
import { niceLinearIncrs, niceTimeIncrs } from './utils';
export interface HeatmapTransformerOptions extends HeatmapCalculationOptions {
@ -61,15 +61,15 @@ export function readHeatmapScanlinesCustomMeta(frame?: DataFrame): HeatmapScanli
return (frame?.meta?.custom ?? {}) as HeatmapScanlinesCustomMeta;
}
export interface BucketsOptions {
export interface RowsHeatmapOptions {
frame: DataFrame;
value?: string; // the field value name
layout?: HeatmapBucketLayout;
layout?: HeatmapCellLayout;
}
/** Given existing buckets, create a values style frame */
// Assumes frames have already been sorted ASC and de-accumulated.
export function bucketsToScanlines(opts: BucketsOptions): DataFrame {
export function rowsToCellsHeatmap(opts: RowsHeatmapOptions): DataFrame {
// TODO: handle null-filling w/ fields[0].config.interval?
const xField = opts.frame.fields[0];
const xValues = xField.values.toArray();
@ -106,13 +106,13 @@ export function bucketsToScanlines(opts: BucketsOptions): DataFrame {
// this name determines whether cells are drawn above, below, or centered on the values
let ordinalFieldName = yFields[0].labels?.le != null ? 'yMax' : 'y';
switch (opts.layout) {
case HeatmapBucketLayout.le:
case HeatmapCellLayout.le:
ordinalFieldName = 'yMax';
break;
case HeatmapBucketLayout.ge:
case HeatmapCellLayout.ge:
ordinalFieldName = 'yMin';
break;
case HeatmapBucketLayout.unknown:
case HeatmapCellLayout.unknown:
ordinalFieldName = 'y';
break;
}
@ -128,7 +128,7 @@ export function bucketsToScanlines(opts: BucketsOptions): DataFrame {
length: xs.length,
refId: opts.frame.refId,
meta: {
type: DataFrameType.HeatmapScanlines,
type: DataFrameType.HeatmapCells,
custom,
},
fields: [
@ -261,7 +261,7 @@ export function calculateHeatmapFromData(frames: DataFrame[], options: HeatmapCa
length: heat2d.x.length,
name: getFieldDisplayName(yField),
meta: {
type: DataFrameType.HeatmapScanlines,
type: DataFrameType.HeatmapCells,
},
fields: [
{

View File

@ -5,7 +5,7 @@ export enum HeatmapCalculationMode {
Count = 'count',
}
export const enum HeatmapBucketLayout {
export const enum HeatmapCellLayout {
le = 'le',
ge = 'ge',
unknown = 'unknown', // unknown

View File

@ -558,7 +558,7 @@ function mergeHeatmapFrames(frames: DataFrame[]): DataFrame[] {
...frames[0],
meta: {
...frames[0].meta,
type: DataFrameType.HeatmapBuckets,
type: DataFrameType.HeatmapRows,
},
fields: [timeField!, ...countFields],
},

View File

@ -4,7 +4,7 @@ import { DataFrameType, Field, FieldType, formattedValueToString, getFieldDispla
import { LinkButton, VerticalGroup } from '@grafana/ui';
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { readHeatmapScanlinesCustomMeta } from 'app/features/transformers/calculateHeatmap/heatmap';
import { HeatmapBucketLayout } from 'app/features/transformers/calculateHeatmap/models.gen';
import { HeatmapCellLayout } from 'app/features/transformers/calculateHeatmap/models.gen';
import { DataHoverView } from '../geomap/components/DataHoverView';
@ -53,8 +53,8 @@ const HeatmapHoverCell = ({ data, hover, showHistogram }: Props) => {
const yValueIdx = index % data.yBucketCount! ?? 0;
const yMinIdx = data.yLayout === HeatmapBucketLayout.le ? yValueIdx - 1 : yValueIdx;
const yMaxIdx = data.yLayout === HeatmapBucketLayout.le ? yValueIdx : yValueIdx + 1;
const yMinIdx = data.yLayout === HeatmapCellLayout.le ? yValueIdx - 1 : yValueIdx;
const yMaxIdx = data.yLayout === HeatmapCellLayout.le ? yValueIdx : yValueIdx + 1;
const yBucketMin = yDispSrc?.[yMinIdx];
const yBucketMax = yDispSrc?.[yMaxIdx];
@ -164,7 +164,7 @@ const HeatmapHoverCell = ({ data, hover, showHistogram }: Props) => {
const renderYBuckets = () => {
switch (data.yLayout) {
case HeatmapBucketLayout.unknown:
case HeatmapCellLayout.unknown:
return <div>{yDisp(yBucketMin)}</div>;
}
return (

View File

@ -143,7 +143,7 @@ export const HeatmapPanel: React.FC<HeatmapPanelProps> = ({
}
let heatmapType = dataRef.current?.heatmap?.meta?.type;
let countFieldIdx = heatmapType === DataFrameType.HeatmapScanlines ? 2 : 3;
let countFieldIdx = heatmapType === DataFrameType.HeatmapCells ? 2 : 3;
const countField = info.heatmap.fields[countFieldIdx];
// TODO -- better would be to get the range from the real color scale!

View File

@ -8,8 +8,8 @@ import {
outerJoinDataFrames,
PanelData,
} from '@grafana/data';
import { calculateHeatmapFromData, bucketsToScanlines } from 'app/features/transformers/calculateHeatmap/heatmap';
import { HeatmapBucketLayout } from 'app/features/transformers/calculateHeatmap/models.gen';
import { calculateHeatmapFromData, rowsToCellsHeatmap } from 'app/features/transformers/calculateHeatmap/heatmap';
import { HeatmapCellLayout } from 'app/features/transformers/calculateHeatmap/models.gen';
import { PanelOptions } from './models.gen';
@ -24,8 +24,8 @@ export interface HeatmapData {
xBucketCount?: number;
yBucketCount?: number;
xLayout?: HeatmapBucketLayout;
yLayout?: HeatmapBucketLayout;
xLayout?: HeatmapCellLayout;
yLayout?: HeatmapCellLayout;
// Print a heatmap cell value
display?: (v: number) => string;
@ -48,34 +48,34 @@ export function prepareHeatmapData(data: PanelData, options: PanelOptions, theme
}
// Check for known heatmap types
let bucketHeatmap: DataFrame | undefined = undefined;
let rowsHeatmap: DataFrame | undefined = undefined;
for (const frame of frames) {
switch (frame.meta?.type) {
case DataFrameType.HeatmapSparse:
return getSparseHeatmapData(frame, exemplars, theme);
case DataFrameType.HeatmapScanlines:
case DataFrameType.HeatmapCells:
return getHeatmapData(frame, exemplars, theme);
case DataFrameType.HeatmapBuckets:
bucketHeatmap = frame; // the default format
case DataFrameType.HeatmapRows:
rowsHeatmap = frame; // the default format
}
}
// Everything past here assumes a field for each row in the heatmap (buckets)
if (!bucketHeatmap) {
if (!rowsHeatmap) {
if (frames.length > 1) {
bucketHeatmap = [
rowsHeatmap = [
outerJoinDataFrames({
frames,
})!,
][0];
} else {
bucketHeatmap = frames[0];
rowsHeatmap = frames[0];
}
}
return getHeatmapData(bucketsToScanlines({ ...options.bucketFrame, frame: bucketHeatmap }), exemplars, theme);
return getHeatmapData(rowsToCellsHeatmap({ ...options.rowsFrame, frame: rowsHeatmap }), exemplars, theme);
}
const getSparseHeatmapData = (
@ -99,7 +99,7 @@ const getSparseHeatmapData = (
};
const getHeatmapData = (frame: DataFrame, exemplars: DataFrame | undefined, theme: GrafanaTheme2): HeatmapData => {
if (frame.meta?.type !== DataFrameType.HeatmapScanlines) {
if (frame.meta?.type !== DataFrameType.HeatmapCells) {
return {
warning: 'Expected heatmap scanlines format',
heatmap: frame,
@ -147,17 +147,9 @@ const getHeatmapData = (frame: DataFrame, exemplars: DataFrame | undefined, them
// TODO: improve heuristic
xLayout:
xName === 'xMax'
? HeatmapBucketLayout.le
: xName === 'xMin'
? HeatmapBucketLayout.ge
: HeatmapBucketLayout.unknown,
xName === 'xMax' ? HeatmapCellLayout.le : xName === 'xMin' ? HeatmapCellLayout.ge : HeatmapCellLayout.unknown,
yLayout:
yName === 'yMax'
? HeatmapBucketLayout.le
: yName === 'yMin'
? HeatmapBucketLayout.ge
: HeatmapBucketLayout.unknown,
yName === 'yMax' ? HeatmapCellLayout.le : yName === 'yMin' ? HeatmapCellLayout.ge : HeatmapCellLayout.unknown,
display: (v) => formattedValueToString(disp(v)),
};

View File

@ -28,9 +28,6 @@ describe('Heatmap Migrations', () => {
"overrides": Array [],
},
"options": Object {
"bucketFrame": Object {
"layout": "auto",
},
"calculate": true,
"calculation": Object {
"xBuckets": Object {
@ -67,6 +64,9 @@ describe('Heatmap Migrations', () => {
"legend": Object {
"show": true,
},
"rowsFrame": Object {
"layout": "auto",
},
"showValue": "never",
"tooltip": Object {
"show": true,

View File

@ -1,7 +1,7 @@
import { FieldConfigSource, PanelModel, PanelTypeChangedHandler } from '@grafana/data';
import { AxisPlacement, ScaleDistribution, VisibilityMode } from '@grafana/schema';
import {
HeatmapBucketLayout,
HeatmapCellLayout,
HeatmapCalculationMode,
HeatmapCalculationOptions,
} from 'app/features/transformers/calculateHeatmap/models.gen';
@ -81,8 +81,8 @@ export function angularToReactHeatmap(angular: any): { fieldConfig: FieldConfigS
min: oldYAxis.min,
max: oldYAxis.max,
},
bucketFrame: {
layout: getHeatmapBucketLayout(angular.yBucketBound),
rowsFrame: {
layout: getHeatmapCellLayout(angular.yBucketBound),
},
legend: {
show: Boolean(angular.legend.show),
@ -127,16 +127,16 @@ export function angularToReactHeatmap(angular: any): { fieldConfig: FieldConfigS
return { fieldConfig, options };
}
function getHeatmapBucketLayout(v?: string): HeatmapBucketLayout {
function getHeatmapCellLayout(v?: string): HeatmapCellLayout {
switch (v) {
case 'upper':
return HeatmapBucketLayout.ge;
return HeatmapCellLayout.ge;
case 'lower':
return HeatmapBucketLayout.le;
return HeatmapCellLayout.le;
case 'middle':
return HeatmapBucketLayout.unknown;
return HeatmapCellLayout.unknown;
}
return HeatmapBucketLayout.auto;
return HeatmapCellLayout.auto;
}
function asNumber(v: any, defaultValue?: number): number | undefined {

View File

@ -4,7 +4,7 @@
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
import { AxisConfig, AxisPlacement, HideableFieldConfig, ScaleDistributionConfig, VisibilityMode } from '@grafana/schema';
import { HeatmapBucketLayout, HeatmapCalculationOptions } from 'app/features/transformers/calculateHeatmap/models.gen';
import { HeatmapCellLayout, HeatmapCalculationOptions } from 'app/features/transformers/calculateHeatmap/models.gen';
export const modelVersion = Object.freeze([1, 0]);
@ -56,9 +56,9 @@ export interface ExemplarConfig {
color: string;
}
export interface BucketFrameOptions {
export interface RowsHeatmapOptions {
value?: string; // value field name
layout?: HeatmapBucketLayout;
layout?: HeatmapCellLayout;
}
export interface PanelOptions {
@ -67,7 +67,7 @@ export interface PanelOptions {
color: HeatmapColorOptions;
filterValues?: FilterValueRange; // was hideZeroBuckets
bucketFrame?: BucketFrameOptions;
rowsFrame?: RowsHeatmapOptions;
showValue: VisibilityMode;
cellGap?: number; // was cardPadding
@ -90,8 +90,8 @@ export const defaultPanelOptions: PanelOptions = {
exponent: 0.5,
steps: 64,
},
bucketFrame: {
layout: HeatmapBucketLayout.auto,
rowsFrame: {
layout: HeatmapCellLayout.auto,
},
yAxis: {
axisPlacement: AxisPlacement.Left,

View File

@ -6,7 +6,7 @@ import { AxisPlacement, GraphFieldConfig, ScaleDistribution, ScaleDistributionCo
import { addHideFrom, ScaleDistributionEditor } from '@grafana/ui/src/options/builder';
import { ColorScale } from 'app/core/components/ColorScale/ColorScale';
import { addHeatmapCalculationOptions } from 'app/features/transformers/calculateHeatmap/editor/helper';
import { HeatmapBucketLayout } from 'app/features/transformers/calculateHeatmap/models.gen';
import { HeatmapCellLayout } from 'app/features/transformers/calculateHeatmap/models.gen';
import { HeatmapPanel } from './HeatmapPanel';
import { heatmapChangedHandler, heatmapMigrationHandler } from './migrations';
@ -125,16 +125,16 @@ export const plugin = new PanelPlugin<PanelOptions, GraphFieldConfig>(HeatmapPan
if (!opts.calculate) {
builder.addRadio({
path: 'bucketFrame.layout',
path: 'rowsFrame.layout',
name: 'Tick alignment',
defaultValue: defaultPanelOptions.bucketFrame?.layout ?? HeatmapBucketLayout.auto,
defaultValue: defaultPanelOptions.rowsFrame?.layout ?? HeatmapCellLayout.auto,
category,
settings: {
options: [
{ label: 'Auto', value: HeatmapBucketLayout.auto },
{ label: 'Top (LE)', value: HeatmapBucketLayout.le },
{ label: 'Middle', value: HeatmapBucketLayout.unknown },
{ label: 'Bottom (GE)', value: HeatmapBucketLayout.ge },
{ label: 'Auto', value: HeatmapCellLayout.auto },
{ label: 'Top (LE)', value: HeatmapCellLayout.le },
{ label: 'Middle', value: HeatmapCellLayout.unknown },
{ label: 'Bottom (GE)', value: HeatmapCellLayout.ge },
],
},
});
@ -326,9 +326,9 @@ export const plugin = new PanelPlugin<PanelOptions, GraphFieldConfig>(HeatmapPan
if (!opts.calculate) {
builder.addTextInput({
path: 'bucketFrame.value',
path: 'rowsFrame.value',
name: 'Cell value name',
defaultValue: defaultPanelOptions.bucketFrame?.value,
defaultValue: defaultPanelOptions.rowsFrame?.value,
settings: {
placeholder: 'Value',
},

View File

@ -13,7 +13,7 @@ import {
import { AxisPlacement, ScaleDirection, ScaleDistribution, ScaleOrientation } from '@grafana/schema';
import { UPlotConfigBuilder } from '@grafana/ui';
import { readHeatmapScanlinesCustomMeta } from 'app/features/transformers/calculateHeatmap/heatmap';
import { HeatmapBucketLayout } from 'app/features/transformers/calculateHeatmap/models.gen';
import { HeatmapCellLayout } from 'app/features/transformers/calculateHeatmap/models.gen';
import { pointWithin, Quadtree, Rect } from '../barchart/quadtree';
@ -270,11 +270,11 @@ export function prepConfig(opts: PrepConfigOpts) {
}
}
if (dataRef.current?.yLayout === HeatmapBucketLayout.le) {
if (dataRef.current?.yLayout === HeatmapCellLayout.le) {
if (!minExpanded) {
dataMin /= yExp;
}
} else if (dataRef.current?.yLayout === HeatmapBucketLayout.ge) {
} else if (dataRef.current?.yLayout === HeatmapCellLayout.ge) {
if (!maxExpanded) {
dataMax *= yExp;
}
@ -292,9 +292,9 @@ export function prepConfig(opts: PrepConfigOpts) {
}
if (bucketSize) {
if (dataRef.current?.yLayout === HeatmapBucketLayout.le) {
if (dataRef.current?.yLayout === HeatmapCellLayout.le) {
dataMin -= bucketSize!;
} else if (dataRef.current?.yLayout === HeatmapBucketLayout.ge) {
} else if (dataRef.current?.yLayout === HeatmapCellLayout.ge) {
dataMax += bucketSize!;
} else {
dataMin -= bucketSize! / 2;
@ -328,10 +328,10 @@ export function prepConfig(opts: PrepConfigOpts) {
let splits = meta.yOrdinalDisplay.map((v, idx) => idx);
switch (dataRef.current?.yLayout) {
case HeatmapBucketLayout.le:
case HeatmapCellLayout.le:
splits.unshift(-1);
break;
case HeatmapBucketLayout.ge:
case HeatmapCellLayout.ge:
splits.push(splits.length);
break;
}
@ -367,7 +367,7 @@ export function prepConfig(opts: PrepConfigOpts) {
: undefined,
});
const pathBuilder = heatmapType === DataFrameType.HeatmapScanlines ? heatmapPathsDense : heatmapPathsSparse;
const pathBuilder = heatmapType === DataFrameType.HeatmapCells ? heatmapPathsDense : heatmapPathsSparse;
// heatmap layer
builder.addSeries({
@ -397,21 +397,21 @@ export function prepConfig(opts: PrepConfigOpts) {
hideLE,
hideGE,
xAlign:
dataRef.current?.xLayout === HeatmapBucketLayout.le
dataRef.current?.xLayout === HeatmapCellLayout.le
? -1
: dataRef.current?.xLayout === HeatmapBucketLayout.ge
: dataRef.current?.xLayout === HeatmapCellLayout.ge
? 1
: 0,
yAlign: ((dataRef.current?.yLayout === HeatmapBucketLayout.le
yAlign: ((dataRef.current?.yLayout === HeatmapCellLayout.le
? -1
: dataRef.current?.yLayout === HeatmapBucketLayout.ge
: dataRef.current?.yLayout === HeatmapCellLayout.ge
? 1
: 0) * (yAxisReverse ? -1 : 1)) as -1 | 0 | 1,
ySizeDivisor,
disp: {
fill: {
values: (u, seriesIdx) => {
let countFacetIdx = heatmapType === DataFrameType.HeatmapScanlines ? 2 : 3;
let countFacetIdx = heatmapType === DataFrameType.HeatmapCells ? 2 : 3;
return valuesToFills(u.data[seriesIdx][countFacetIdx] as unknown as number[], palette, valueMin, valueMax);
},
index: palette,