Heatmap: improve new heatmap options (#49201)

This commit is contained in:
Ryan McKinley 2022-05-18 16:54:32 -07:00 committed by GitHub
parent 2053d37d7a
commit d0d4d8af7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 98 additions and 64 deletions

View File

@ -190,6 +190,10 @@ export function calculateHeatmapFromData(frames: DataFrame[], options: HeatmapCa
throw 'no heatmap fields found'; throw 'no heatmap fields found';
} }
if (!xs.length || !ys.length) {
throw 'no values found';
}
const heat2d = heatmap(xs, ys, { const heat2d = heatmap(xs, ys, {
xSorted: true, xSorted: true,
xTime: xField.type === FieldType.time, xTime: xField.type === FieldType.time,

View File

@ -3,15 +3,7 @@ import React, { useCallback, useMemo, useRef, useState } from 'react';
import { DataFrameType, GrafanaTheme2, PanelProps, reduceField, ReducerID, TimeRange } from '@grafana/data'; import { DataFrameType, GrafanaTheme2, PanelProps, reduceField, ReducerID, TimeRange } from '@grafana/data';
import { PanelDataErrorView } from '@grafana/runtime'; import { PanelDataErrorView } from '@grafana/runtime';
import { import { Portal, UPlotChart, useStyles2, useTheme2, VizLayout, VizTooltipContainer } from '@grafana/ui';
Portal,
UPlotChart,
useStyles2,
useTheme2,
VizLayout,
VizTooltipContainer,
LegendDisplayMode,
} from '@grafana/ui';
import { CloseButton } from 'app/core/components/CloseButton/CloseButton'; import { CloseButton } from 'app/core/components/CloseButton/CloseButton';
import { ColorScale } from 'app/core/components/ColorScale/ColorScale'; import { ColorScale } from 'app/core/components/ColorScale/ColorScale';
@ -42,7 +34,13 @@ export const HeatmapPanel: React.FC<HeatmapPanelProps> = ({
let timeRangeRef = useRef<TimeRange>(timeRange); let timeRangeRef = useRef<TimeRange>(timeRange);
timeRangeRef.current = timeRange; timeRangeRef.current = timeRange;
const info = useMemo(() => prepareHeatmapData(data, options, theme), [data, options, theme]); const info = useMemo(() => {
try {
return prepareHeatmapData(data, options, theme);
} catch (ex) {
return { warning: `${ex}` };
}
}, [data, options, theme]);
const facets = useMemo(() => [null, info.heatmap?.fields.map((f) => f.values.toArray())], [info.heatmap]); const facets = useMemo(() => [null, info.heatmap?.fields.map((f) => f.values.toArray())], [info.heatmap]);
@ -86,7 +84,10 @@ export const HeatmapPanel: React.FC<HeatmapPanelProps> = ({
onhover: onhover, onhover: onhover,
onclick: options.tooltip.show ? onclick : null, onclick: options.tooltip.show ? onclick : null,
onzoom: (evt) => { onzoom: (evt) => {
const delta = evt.xMax - evt.xMin;
if (delta > 1) {
onChangeTimeRange({ from: evt.xMin, to: evt.xMax }); onChangeTimeRange({ from: evt.xMin, to: evt.xMax });
}
}, },
isToolTipOpen, isToolTipOpen,
timeZone, timeZone,
@ -99,7 +100,7 @@ export const HeatmapPanel: React.FC<HeatmapPanelProps> = ({
}, [options, data.structureRev]); }, [options, data.structureRev]);
const renderLegend = () => { const renderLegend = () => {
if (options.legend.displayMode === LegendDisplayMode.Hidden || !info.heatmap) { if (!info.heatmap || !options.legend.show) {
return null; return null;
} }

View File

@ -32,7 +32,7 @@ describe('Heatmap Migrations', () => {
"fill": "dark-orange", "fill": "dark-orange",
"mode": "scheme", "mode": "scheme",
"scale": "exponential", "scale": "exponential",
"scheme": "Oranges", "scheme": "BuGn",
"steps": 256, "steps": 256,
}, },
"heatmap": Object { "heatmap": Object {
@ -46,9 +46,7 @@ describe('Heatmap Migrations', () => {
}, },
}, },
"legend": Object { "legend": Object {
"calcs": Array [], "show": true,
"displayMode": "list",
"placement": "bottom",
}, },
"showValue": "never", "showValue": "never",
"source": "calculate", "source": "calculate",

View File

@ -1,11 +1,12 @@
import { FieldConfigSource, PanelModel, PanelTypeChangedHandler } from '@grafana/data'; import { FieldConfigSource, PanelModel, PanelTypeChangedHandler } from '@grafana/data';
import { LegendDisplayMode, VisibilityMode } from '@grafana/schema'; import { VisibilityMode } from '@grafana/schema';
import { import {
HeatmapCalculationMode, HeatmapCalculationMode,
HeatmapCalculationOptions, HeatmapCalculationOptions,
} from 'app/features/transformers/calculateHeatmap/models.gen'; } from 'app/features/transformers/calculateHeatmap/models.gen';
import { HeatmapSourceMode, PanelOptions, defaultPanelOptions } from './models.gen'; import { HeatmapSourceMode, PanelOptions, defaultPanelOptions, HeatmapColorMode } from './models.gen';
import { colorSchemes } from './palettes';
/** /**
* This is called when the panel changes from another panel * This is called when the panel changes from another panel
@ -59,9 +60,7 @@ export function angularToReactHeatmap(angular: any): { fieldConfig: FieldConfigS
yAxisLabels: angular.yBucketBound, yAxisLabels: angular.yBucketBound,
yAxisReverse: angular.reverseYBuckets, yAxisReverse: angular.reverseYBuckets,
legend: { legend: {
displayMode: angular.legend.show ? LegendDisplayMode.List : LegendDisplayMode.Hidden, show: Boolean(angular.legend.show),
calcs: [],
placement: 'bottom',
}, },
showValue: VisibilityMode.Never, showValue: VisibilityMode.Never,
tooltip: { tooltip: {
@ -70,6 +69,27 @@ export function angularToReactHeatmap(angular: any): { fieldConfig: FieldConfigS
}, },
}; };
// Migrate color options
const color = angular.color;
switch (color?.mode) {
case 'spectrum': {
options.color.mode = HeatmapColorMode.Scheme;
const current = color.colorScheme as string;
let scheme = colorSchemes.find((v) => v.name === current);
if (!scheme) {
scheme = colorSchemes.find((v) => current.indexOf(v.name) >= 0);
}
options.color.scheme = scheme ? scheme.name : defaultPanelOptions.color.scheme;
break;
}
case 'opacity': {
options.color.mode = HeatmapColorMode.Opacity;
options.color.scale = color.scale;
break;
}
}
return { fieldConfig, options }; return { fieldConfig, options };
} }
@ -79,6 +99,9 @@ function asNumber(v: any): number | undefined {
} }
export const heatmapMigrationHandler = (panel: PanelModel): Partial<PanelOptions> => { export const heatmapMigrationHandler = (panel: PanelModel): Partial<PanelOptions> => {
// Nothing yet // Migrating from angular
if (!panel.pluginVersion && Object.keys(panel.options).length === 0) {
return heatmapChangedHandler(panel, 'heatmap', { angular: panel }, panel.fieldConfig);
}
return panel.options; return panel.options;
}; };

View File

@ -3,7 +3,7 @@
// It is currenty hand written but will serve as the target for cuetsy // It is currenty hand written but will serve as the target for cuetsy
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
import { HideableFieldConfig, LegendDisplayMode, OptionsWithLegend, VisibilityMode } from '@grafana/schema'; import { HideableFieldConfig, VisibilityMode } from '@grafana/schema';
import { HeatmapCalculationOptions } from 'app/features/transformers/calculateHeatmap/models.gen'; import { HeatmapCalculationOptions } from 'app/features/transformers/calculateHeatmap/models.gen';
export const modelVersion = Object.freeze([1, 0]); export const modelVersion = Object.freeze([1, 0]);
@ -42,8 +42,11 @@ export interface HeatmapTooltip {
show: boolean; show: boolean;
yHistogram?: boolean; yHistogram?: boolean;
} }
export interface HeatmapLegend {
show: boolean;
}
export interface PanelOptions extends OptionsWithLegend { export interface PanelOptions {
source: HeatmapSourceMode; source: HeatmapSourceMode;
color: HeatmapColorOptions; color: HeatmapColorOptions;
@ -56,6 +59,7 @@ export interface PanelOptions extends OptionsWithLegend {
hideThreshold?: number; // was hideZeroBuckets hideThreshold?: number; // was hideZeroBuckets
yAxisLabels?: string; yAxisLabels?: string;
yAxisReverse?: boolean; yAxisReverse?: boolean;
legend: HeatmapLegend;
tooltip: HeatmapTooltip; tooltip: HeatmapTooltip;
} }
@ -71,15 +75,13 @@ export const defaultPanelOptions: PanelOptions = {
steps: 64, steps: 64,
}, },
showValue: VisibilityMode.Auto, showValue: VisibilityMode.Auto,
legend: {
displayMode: LegendDisplayMode.Hidden,
placement: 'bottom',
calcs: [],
},
tooltip: { tooltip: {
show: true, show: true,
yHistogram: false, yHistogram: false,
}, },
legend: {
show: true,
},
cellGap: 1, cellGap: 1,
}; };

View File

@ -1,14 +1,13 @@
import React from 'react'; import React from 'react';
import { Field, FieldType, PanelPlugin } from '@grafana/data'; import { Field, FieldConfigProperty, FieldType, PanelPlugin } from '@grafana/data';
import { config } from '@grafana/runtime'; import { config } from '@grafana/runtime';
import { GraphFieldConfig, VisibilityMode } from '@grafana/schema'; import { GraphFieldConfig } from '@grafana/schema';
import { commonOptionsBuilder } from '@grafana/ui';
import { ColorScale } from 'app/core/components/ColorScale/ColorScale'; import { ColorScale } from 'app/core/components/ColorScale/ColorScale';
import { addHeatmapCalculationOptions } from 'app/features/transformers/calculateHeatmap/editor/helper'; import { addHeatmapCalculationOptions } from 'app/features/transformers/calculateHeatmap/editor/helper';
import { HeatmapPanel } from './HeatmapPanel'; import { HeatmapPanel } from './HeatmapPanel';
import { heatmapChangedHandler } from './migrations'; import { heatmapChangedHandler, heatmapMigrationHandler } from './migrations';
import { import {
PanelOptions, PanelOptions,
defaultPanelOptions, defaultPanelOptions,
@ -20,9 +19,11 @@ import { colorSchemes, quantizeScheme } from './palettes';
import { HeatmapSuggestionsSupplier } from './suggestions'; import { HeatmapSuggestionsSupplier } from './suggestions';
export const plugin = new PanelPlugin<PanelOptions, GraphFieldConfig>(HeatmapPanel) export const plugin = new PanelPlugin<PanelOptions, GraphFieldConfig>(HeatmapPanel)
.useFieldConfig() .useFieldConfig({
disableStandardOptions: [FieldConfigProperty.Color, FieldConfigProperty.Thresholds],
})
.setPanelChangeHandler(heatmapChangedHandler) .setPanelChangeHandler(heatmapChangedHandler)
// .setMigrationHandler(heatmapMigrationHandler) .setMigrationHandler(heatmapMigrationHandler)
.setPanelOptions((builder, context) => { .setPanelOptions((builder, context) => {
const opts = context.options ?? defaultPanelOptions; const opts = context.options ?? defaultPanelOptions;
@ -155,19 +156,19 @@ export const plugin = new PanelPlugin<PanelOptions, GraphFieldConfig>(HeatmapPan
category = ['Display']; category = ['Display'];
builder builder
.addRadio({ // .addRadio({
path: 'showValue', // path: 'showValue',
name: 'Show values', // name: 'Show values',
defaultValue: defaultPanelOptions.showValue, // defaultValue: defaultPanelOptions.showValue,
category, // category,
settings: { // settings: {
options: [ // options: [
{ value: VisibilityMode.Auto, label: 'Auto' }, // { value: VisibilityMode.Auto, label: 'Auto' },
{ value: VisibilityMode.Always, label: 'Always' }, // { value: VisibilityMode.Always, label: 'Always' },
{ value: VisibilityMode.Never, label: 'Never' }, // { value: VisibilityMode.Never, label: 'Never' },
], // ],
}, // },
}) // })
.addNumberInput({ .addNumberInput({
path: 'hideThreshold', path: 'hideThreshold',
name: 'Hide cell counts <=', name: 'Hide cell counts <=',
@ -194,20 +195,20 @@ export const plugin = new PanelPlugin<PanelOptions, GraphFieldConfig>(HeatmapPan
// max: 100, // max: 100,
// }, // },
// }) // })
.addRadio({ // .addRadio({
path: 'yAxisLabels', // path: 'yAxisLabels',
name: 'Axis labels', // name: 'Axis labels',
defaultValue: 'auto', // defaultValue: 'auto',
category, // category,
settings: { // settings: {
options: [ // options: [
{ value: 'auto', label: 'Auto' }, // { value: 'auto', label: 'Auto' },
{ value: 'middle', label: 'Middle' }, // { value: 'middle', label: 'Middle' },
{ value: 'bottom', label: 'Bottom' }, // { value: 'bottom', label: 'Bottom' },
{ value: 'top', label: 'Top' }, // { value: 'top', label: 'Top' },
], // ],
}, // },
}) // })
.addBooleanSwitch({ .addBooleanSwitch({
path: 'yAxisReverse', path: 'yAxisReverse',
name: 'Reverse buckets', name: 'Reverse buckets',
@ -232,7 +233,12 @@ export const plugin = new PanelPlugin<PanelOptions, GraphFieldConfig>(HeatmapPan
showIf: (opts) => opts.tooltip.show, showIf: (opts) => opts.tooltip.show,
}); });
// custom legend? category = ['Legend'];
commonOptionsBuilder.addLegendOptions(builder); builder.addBooleanSwitch({
path: 'legend.show',
name: 'Show legend',
defaultValue: defaultPanelOptions.legend.show,
category,
});
}) })
.setSuggestionsSupplier(new HeatmapSuggestionsSupplier()); .setSuggestionsSupplier(new HeatmapSuggestionsSupplier());