diff --git a/public/app/features/transformers/calculateHeatmap/heatmap.ts b/public/app/features/transformers/calculateHeatmap/heatmap.ts index 5b7e3d8b8a6..265c8a74fb4 100644 --- a/public/app/features/transformers/calculateHeatmap/heatmap.ts +++ b/public/app/features/transformers/calculateHeatmap/heatmap.ts @@ -190,6 +190,10 @@ export function calculateHeatmapFromData(frames: DataFrame[], options: HeatmapCa throw 'no heatmap fields found'; } + if (!xs.length || !ys.length) { + throw 'no values found'; + } + const heat2d = heatmap(xs, ys, { xSorted: true, xTime: xField.type === FieldType.time, diff --git a/public/app/plugins/panel/heatmap-new/HeatmapPanel.tsx b/public/app/plugins/panel/heatmap-new/HeatmapPanel.tsx index b10672a459a..71910c75daa 100644 --- a/public/app/plugins/panel/heatmap-new/HeatmapPanel.tsx +++ b/public/app/plugins/panel/heatmap-new/HeatmapPanel.tsx @@ -3,15 +3,7 @@ import React, { useCallback, useMemo, useRef, useState } from 'react'; import { DataFrameType, GrafanaTheme2, PanelProps, reduceField, ReducerID, TimeRange } from '@grafana/data'; import { PanelDataErrorView } from '@grafana/runtime'; -import { - Portal, - UPlotChart, - useStyles2, - useTheme2, - VizLayout, - VizTooltipContainer, - LegendDisplayMode, -} from '@grafana/ui'; +import { Portal, UPlotChart, useStyles2, useTheme2, VizLayout, VizTooltipContainer } from '@grafana/ui'; import { CloseButton } from 'app/core/components/CloseButton/CloseButton'; import { ColorScale } from 'app/core/components/ColorScale/ColorScale'; @@ -42,7 +34,13 @@ export const HeatmapPanel: React.FC = ({ let timeRangeRef = useRef(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]); @@ -86,7 +84,10 @@ export const HeatmapPanel: React.FC = ({ onhover: onhover, onclick: options.tooltip.show ? onclick : null, onzoom: (evt) => { - onChangeTimeRange({ from: evt.xMin, to: evt.xMax }); + const delta = evt.xMax - evt.xMin; + if (delta > 1) { + onChangeTimeRange({ from: evt.xMin, to: evt.xMax }); + } }, isToolTipOpen, timeZone, @@ -99,7 +100,7 @@ export const HeatmapPanel: React.FC = ({ }, [options, data.structureRev]); const renderLegend = () => { - if (options.legend.displayMode === LegendDisplayMode.Hidden || !info.heatmap) { + if (!info.heatmap || !options.legend.show) { return null; } diff --git a/public/app/plugins/panel/heatmap-new/migrations.test.ts b/public/app/plugins/panel/heatmap-new/migrations.test.ts index 43e5465d0c4..511f7a95d6d 100644 --- a/public/app/plugins/panel/heatmap-new/migrations.test.ts +++ b/public/app/plugins/panel/heatmap-new/migrations.test.ts @@ -32,7 +32,7 @@ describe('Heatmap Migrations', () => { "fill": "dark-orange", "mode": "scheme", "scale": "exponential", - "scheme": "Oranges", + "scheme": "BuGn", "steps": 256, }, "heatmap": Object { @@ -46,9 +46,7 @@ describe('Heatmap Migrations', () => { }, }, "legend": Object { - "calcs": Array [], - "displayMode": "list", - "placement": "bottom", + "show": true, }, "showValue": "never", "source": "calculate", diff --git a/public/app/plugins/panel/heatmap-new/migrations.ts b/public/app/plugins/panel/heatmap-new/migrations.ts index 9daea701438..e6eb2a5018f 100644 --- a/public/app/plugins/panel/heatmap-new/migrations.ts +++ b/public/app/plugins/panel/heatmap-new/migrations.ts @@ -1,11 +1,12 @@ import { FieldConfigSource, PanelModel, PanelTypeChangedHandler } from '@grafana/data'; -import { LegendDisplayMode, VisibilityMode } from '@grafana/schema'; +import { VisibilityMode } from '@grafana/schema'; import { HeatmapCalculationMode, HeatmapCalculationOptions, } 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 @@ -59,9 +60,7 @@ export function angularToReactHeatmap(angular: any): { fieldConfig: FieldConfigS yAxisLabels: angular.yBucketBound, yAxisReverse: angular.reverseYBuckets, legend: { - displayMode: angular.legend.show ? LegendDisplayMode.List : LegendDisplayMode.Hidden, - calcs: [], - placement: 'bottom', + show: Boolean(angular.legend.show), }, showValue: VisibilityMode.Never, 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 }; } @@ -79,6 +99,9 @@ function asNumber(v: any): number | undefined { } export const heatmapMigrationHandler = (panel: PanelModel): Partial => { - // 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; }; diff --git a/public/app/plugins/panel/heatmap-new/models.gen.ts b/public/app/plugins/panel/heatmap-new/models.gen.ts index a8c2302a528..16912388ef6 100644 --- a/public/app/plugins/panel/heatmap-new/models.gen.ts +++ b/public/app/plugins/panel/heatmap-new/models.gen.ts @@ -3,7 +3,7 @@ // 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'; export const modelVersion = Object.freeze([1, 0]); @@ -42,8 +42,11 @@ export interface HeatmapTooltip { show: boolean; yHistogram?: boolean; } +export interface HeatmapLegend { + show: boolean; +} -export interface PanelOptions extends OptionsWithLegend { +export interface PanelOptions { source: HeatmapSourceMode; color: HeatmapColorOptions; @@ -56,6 +59,7 @@ export interface PanelOptions extends OptionsWithLegend { hideThreshold?: number; // was hideZeroBuckets yAxisLabels?: string; yAxisReverse?: boolean; + legend: HeatmapLegend; tooltip: HeatmapTooltip; } @@ -71,15 +75,13 @@ export const defaultPanelOptions: PanelOptions = { steps: 64, }, showValue: VisibilityMode.Auto, - legend: { - displayMode: LegendDisplayMode.Hidden, - placement: 'bottom', - calcs: [], - }, tooltip: { show: true, yHistogram: false, }, + legend: { + show: true, + }, cellGap: 1, }; diff --git a/public/app/plugins/panel/heatmap-new/module.tsx b/public/app/plugins/panel/heatmap-new/module.tsx index 397e1f0d1fe..6031f8b28d2 100644 --- a/public/app/plugins/panel/heatmap-new/module.tsx +++ b/public/app/plugins/panel/heatmap-new/module.tsx @@ -1,14 +1,13 @@ 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 { GraphFieldConfig, VisibilityMode } from '@grafana/schema'; -import { commonOptionsBuilder } from '@grafana/ui'; +import { GraphFieldConfig } from '@grafana/schema'; import { ColorScale } from 'app/core/components/ColorScale/ColorScale'; import { addHeatmapCalculationOptions } from 'app/features/transformers/calculateHeatmap/editor/helper'; import { HeatmapPanel } from './HeatmapPanel'; -import { heatmapChangedHandler } from './migrations'; +import { heatmapChangedHandler, heatmapMigrationHandler } from './migrations'; import { PanelOptions, defaultPanelOptions, @@ -20,9 +19,11 @@ import { colorSchemes, quantizeScheme } from './palettes'; import { HeatmapSuggestionsSupplier } from './suggestions'; export const plugin = new PanelPlugin(HeatmapPanel) - .useFieldConfig() + .useFieldConfig({ + disableStandardOptions: [FieldConfigProperty.Color, FieldConfigProperty.Thresholds], + }) .setPanelChangeHandler(heatmapChangedHandler) - // .setMigrationHandler(heatmapMigrationHandler) + .setMigrationHandler(heatmapMigrationHandler) .setPanelOptions((builder, context) => { const opts = context.options ?? defaultPanelOptions; @@ -155,19 +156,19 @@ export const plugin = new PanelPlugin(HeatmapPan category = ['Display']; builder - .addRadio({ - path: 'showValue', - name: 'Show values', - defaultValue: defaultPanelOptions.showValue, - category, - settings: { - options: [ - { value: VisibilityMode.Auto, label: 'Auto' }, - { value: VisibilityMode.Always, label: 'Always' }, - { value: VisibilityMode.Never, label: 'Never' }, - ], - }, - }) + // .addRadio({ + // path: 'showValue', + // name: 'Show values', + // defaultValue: defaultPanelOptions.showValue, + // category, + // settings: { + // options: [ + // { value: VisibilityMode.Auto, label: 'Auto' }, + // { value: VisibilityMode.Always, label: 'Always' }, + // { value: VisibilityMode.Never, label: 'Never' }, + // ], + // }, + // }) .addNumberInput({ path: 'hideThreshold', name: 'Hide cell counts <=', @@ -194,20 +195,20 @@ export const plugin = new PanelPlugin(HeatmapPan // max: 100, // }, // }) - .addRadio({ - path: 'yAxisLabels', - name: 'Axis labels', - defaultValue: 'auto', - category, - settings: { - options: [ - { value: 'auto', label: 'Auto' }, - { value: 'middle', label: 'Middle' }, - { value: 'bottom', label: 'Bottom' }, - { value: 'top', label: 'Top' }, - ], - }, - }) + // .addRadio({ + // path: 'yAxisLabels', + // name: 'Axis labels', + // defaultValue: 'auto', + // category, + // settings: { + // options: [ + // { value: 'auto', label: 'Auto' }, + // { value: 'middle', label: 'Middle' }, + // { value: 'bottom', label: 'Bottom' }, + // { value: 'top', label: 'Top' }, + // ], + // }, + // }) .addBooleanSwitch({ path: 'yAxisReverse', name: 'Reverse buckets', @@ -232,7 +233,12 @@ export const plugin = new PanelPlugin(HeatmapPan showIf: (opts) => opts.tooltip.show, }); - // custom legend? - commonOptionsBuilder.addLegendOptions(builder); + category = ['Legend']; + builder.addBooleanSwitch({ + path: 'legend.show', + name: 'Show legend', + defaultValue: defaultPanelOptions.legend.show, + category, + }); }) .setSuggestionsSupplier(new HeatmapSuggestionsSupplier());