HeatmapNG: consolidate frame types & fix color ranging (#51089)

* rename yZeroDisplay -> yMinDisplay
* remove heatmap-cells-sparse frame type
* parse x bucket size to millis
* take into account hideLE & hideGE filters to auto-range color scale
* extract cell value range scanning to heatmapData
pull/51144/head
Leon Sorokin 3 years ago committed by GitHub
parent c2aee2b6da
commit f4f31b40fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      packages/grafana-data/src/types/dataFrameTypes.ts
  2. 4
      pkg/util/converter/prom.go
  3. 4
      pkg/util/converter/testdata/prom-matrix-histogram-no-labels-frame.jsonc
  4. 4
      pkg/util/converter/testdata/prom-matrix-histogram-no-labels-wide-frame.jsonc
  5. 96
      pkg/util/converter/testdata/prom-matrix-histogram-partitioned-frame.jsonc
  6. 96
      pkg/util/converter/testdata/prom-matrix-histogram-partitioned-wide-frame.jsonc
  7. 4
      pkg/util/converter/testdata/prom-vector-histogram-no-labels-frame.jsonc
  8. 4
      pkg/util/converter/testdata/prom-vector-histogram-no-labels-wide-frame.jsonc
  9. 33
      public/app/features/transformers/calculateHeatmap/heatmap.ts
  10. 10
      public/app/plugins/panel/heatmap-new/HeatmapHoverView.tsx
  11. 29
      public/app/plugins/panel/heatmap-new/HeatmapPanel.tsx
  12. 42
      public/app/plugins/panel/heatmap-new/fields.ts
  13. 40
      public/app/plugins/panel/heatmap-new/utils.ts

@ -26,11 +26,4 @@ export enum DataFrameType {
* If the y value is actually ordinal, use `meta.custom` to specify the bucket lookup values
*/
HeatmapCells = 'heatmap-cells',
/**
* WIP sparse heatmap support
*
* @private
*/
HeatmapSparse = 'heatmap-cells-sparse',
}

@ -429,7 +429,7 @@ func readMatrixOrVectorWide(iter *jsoniter.Iterator, resultType string) *backend
histogram.yMin.Labels = valueField.Labels
frame := data.NewFrame(valueField.Name, histogram.time, histogram.yMin, histogram.yMax, histogram.count, histogram.yLayout)
frame.Meta = &data.FrameMeta{
Type: "heatmap-cells-sparse",
Type: "heatmap-cells",
}
if frame.Name == data.TimeSeriesValueFieldName {
frame.Name = "" // only set the name if useful
@ -535,7 +535,7 @@ func readMatrixOrVectorMulti(iter *jsoniter.Iterator, resultType string) *backen
histogram.yMin.Labels = valueField.Labels
frame := data.NewFrame(valueField.Name, histogram.time, histogram.yMin, histogram.yMax, histogram.count, histogram.yLayout)
frame.Meta = &data.FrameMeta{
Type: "heatmap-cells-sparse",
Type: "heatmap-cells",
}
if frame.Name == data.TimeSeriesValueFieldName {
frame.Name = "" // only set the name if useful

@ -1,7 +1,7 @@
// 🌟 This was machine generated. Do not edit. 🌟
//
// Frame[0] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 932 Rows
@ -29,7 +29,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{

@ -1,7 +1,7 @@
// 🌟 This was machine generated. Do not edit. 🌟
//
// Frame[0] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 932 Rows
@ -29,7 +29,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{

@ -1,7 +1,7 @@
// 🌟 This was machine generated. Do not edit. 🌟
//
// Frame[0] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 0 Rows
@ -15,7 +15,7 @@
//
//
// Frame[1] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 0 Rows
@ -29,7 +29,7 @@
//
//
// Frame[2] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 0 Rows
@ -43,7 +43,7 @@
//
//
// Frame[3] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 0 Rows
@ -57,7 +57,7 @@
//
//
// Frame[4] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 0 Rows
@ -71,7 +71,7 @@
//
//
// Frame[5] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 0 Rows
@ -85,7 +85,7 @@
//
//
// Frame[6] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 0 Rows
@ -99,7 +99,7 @@
//
//
// Frame[7] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 0 Rows
@ -113,7 +113,7 @@
//
//
// Frame[8] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 1 Rows
@ -128,7 +128,7 @@
//
//
// Frame[9] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 0 Rows
@ -142,7 +142,7 @@
//
//
// Frame[10] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 426 Rows
@ -166,7 +166,7 @@
//
//
// Frame[11] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 1 Rows
@ -181,7 +181,7 @@
//
//
// Frame[12] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 6 Rows
@ -201,7 +201,7 @@
//
//
// Frame[13] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 269 Rows
@ -225,7 +225,7 @@
//
//
// Frame[14] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 303 Rows
@ -249,7 +249,7 @@
//
//
// Frame[15] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 56 Rows
@ -273,7 +273,7 @@
//
//
// Frame[16] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 41 Rows
@ -297,7 +297,7 @@
//
//
// Frame[17] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 29 Rows
@ -321,7 +321,7 @@
//
//
// Frame[18] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 38 Rows
@ -345,7 +345,7 @@
//
//
// Frame[19] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 195 Rows
@ -369,7 +369,7 @@
//
//
// Frame[20] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 261 Rows
@ -393,7 +393,7 @@
//
//
// Frame[21] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 176 Rows
@ -417,7 +417,7 @@
//
//
// Frame[22] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 255 Rows
@ -441,7 +441,7 @@
//
//
// Frame[23] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 167 Rows
@ -469,7 +469,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -527,7 +527,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -585,7 +585,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -643,7 +643,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -701,7 +701,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -759,7 +759,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -817,7 +817,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -875,7 +875,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -933,7 +933,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -1001,7 +1001,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -1059,7 +1059,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -3252,7 +3252,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -3320,7 +3320,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -3413,7 +3413,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -4821,7 +4821,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -6399,7 +6399,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -6742,7 +6742,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -7010,7 +7010,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -7218,7 +7218,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -7471,7 +7471,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -8509,7 +8509,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -9877,7 +9877,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -10820,7 +10820,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -12158,7 +12158,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{

@ -1,7 +1,7 @@
// 🌟 This was machine generated. Do not edit. 🌟
//
// Frame[0] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 0 Rows
@ -15,7 +15,7 @@
//
//
// Frame[1] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 0 Rows
@ -29,7 +29,7 @@
//
//
// Frame[2] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 0 Rows
@ -43,7 +43,7 @@
//
//
// Frame[3] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 0 Rows
@ -57,7 +57,7 @@
//
//
// Frame[4] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 0 Rows
@ -71,7 +71,7 @@
//
//
// Frame[5] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 0 Rows
@ -85,7 +85,7 @@
//
//
// Frame[6] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 0 Rows
@ -99,7 +99,7 @@
//
//
// Frame[7] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 0 Rows
@ -113,7 +113,7 @@
//
//
// Frame[8] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 1 Rows
@ -128,7 +128,7 @@
//
//
// Frame[9] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 0 Rows
@ -142,7 +142,7 @@
//
//
// Frame[10] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 426 Rows
@ -166,7 +166,7 @@
//
//
// Frame[11] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 1 Rows
@ -181,7 +181,7 @@
//
//
// Frame[12] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 6 Rows
@ -201,7 +201,7 @@
//
//
// Frame[13] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 269 Rows
@ -225,7 +225,7 @@
//
//
// Frame[14] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 303 Rows
@ -249,7 +249,7 @@
//
//
// Frame[15] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 56 Rows
@ -273,7 +273,7 @@
//
//
// Frame[16] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 41 Rows
@ -297,7 +297,7 @@
//
//
// Frame[17] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 29 Rows
@ -321,7 +321,7 @@
//
//
// Frame[18] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 38 Rows
@ -345,7 +345,7 @@
//
//
// Frame[19] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 195 Rows
@ -369,7 +369,7 @@
//
//
// Frame[20] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 261 Rows
@ -393,7 +393,7 @@
//
//
// Frame[21] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 176 Rows
@ -417,7 +417,7 @@
//
//
// Frame[22] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 255 Rows
@ -441,7 +441,7 @@
//
//
// Frame[23] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 167 Rows
@ -469,7 +469,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -527,7 +527,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -585,7 +585,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -643,7 +643,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -701,7 +701,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -759,7 +759,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -817,7 +817,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -875,7 +875,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -933,7 +933,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -1001,7 +1001,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -1059,7 +1059,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -3252,7 +3252,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -3320,7 +3320,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -3413,7 +3413,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -4821,7 +4821,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -6399,7 +6399,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -6742,7 +6742,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -7010,7 +7010,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -7218,7 +7218,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -7471,7 +7471,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -8509,7 +8509,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -9877,7 +9877,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -10820,7 +10820,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{
@ -12158,7 +12158,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{

@ -1,7 +1,7 @@
// 🌟 This was machine generated. Do not edit. 🌟
//
// Frame[0] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 134 Rows
@ -29,7 +29,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{

@ -1,7 +1,7 @@
// 🌟 This was machine generated. Do not edit. 🌟
//
// Frame[0] {
// "type": "heatmap-cells-sparse"
// "type": "heatmap-cells"
// }
// Name:
// Dimensions: 5 Fields by 134 Rows
@ -29,7 +29,7 @@
{
"schema": {
"meta": {
"type": "heatmap-cells-sparse"
"type": "heatmap-cells"
},
"fields": [
{

@ -13,6 +13,8 @@ import {
Field,
getValueFormat,
formattedValueToString,
durationToMilliseconds,
parseDuration,
} from '@grafana/data';
import { ScaleDistribution } from '@grafana/schema';
@ -56,7 +58,7 @@ export interface HeatmapRowsCustomMeta {
yOrdinalDisplay: string[];
yOrdinalLabel?: string[];
yMatchWithLabel?: string;
yZeroDisplay?: string;
yMinDisplay?: string;
}
/** simple utility to get heatmap metadata from a frame */
@ -64,6 +66,26 @@ export function readHeatmapRowsCustomMeta(frame?: DataFrame): HeatmapRowsCustomM
return (frame?.meta?.custom ?? {}) as HeatmapRowsCustomMeta;
}
export function isHeatmapCellsDense(frame: DataFrame) {
let foundY = false;
for (let field of frame.fields) {
// dense heatmap frames can only have one of these fields
switch (field.name) {
case 'y':
case 'yMin':
case 'yMax':
if (foundY) {
return false;
}
foundY = true;
}
}
return foundY;
}
export interface RowsHeatmapOptions {
frame: DataFrame;
value?: string; // the field value name
@ -129,7 +151,7 @@ export function rowsToCellsHeatmap(opts: RowsHeatmapOptions): DataFrame {
if (custom.yMatchWithLabel) {
custom.yOrdinalLabel = yFields.map((f) => f.labels?.[custom.yMatchWithLabel!] ?? '');
if (custom.yMatchWithLabel === 'le') {
custom.yZeroDisplay = '0.0';
custom.yMinDisplay = '0.0';
}
}
@ -137,8 +159,8 @@ export function rowsToCellsHeatmap(opts: RowsHeatmapOptions): DataFrame {
// TODO: this leaves the internally prepended '0.0' without this formatting treatment
if (opts.unit?.length || opts.decimals != null) {
const fmt = getValueFormat(opts.unit ?? 'short');
if (custom.yZeroDisplay) {
custom.yZeroDisplay = formattedValueToString(fmt(0, opts.decimals));
if (custom.yMinDisplay) {
custom.yMinDisplay = formattedValueToString(fmt(0, opts.decimals));
}
custom.yOrdinalDisplay = custom.yOrdinalDisplay.map((name) => {
let num = +name;
@ -274,11 +296,12 @@ export function calculateHeatmapFromData(frames: DataFrame[], options: HeatmapCa
const scaleDistribution = options.yBuckets?.scale ?? {
type: ScaleDistribution.Linear,
};
const heat2d = heatmap(xs, ys, {
xSorted: true,
xTime: xField.type === FieldType.time,
xMode: xBucketsCfg.mode,
xSize: xBucketsCfg.value ? +xBucketsCfg.value : undefined,
xSize: durationToMilliseconds(parseDuration(xBucketsCfg.value ?? '')),
yMode: yBucketsCfg.mode,
ySize: yBucketsCfg.value ? +yBucketsCfg.value : undefined,
yLog: scaleDistribution?.type === ScaleDistribution.Log ? (scaleDistribution?.log as any) : undefined,

@ -1,9 +1,9 @@
import React, { useEffect, useRef } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { DataFrameType, Field, FieldType, formattedValueToString, getFieldDisplayName, LinkModel } from '@grafana/data';
import { LinkButton, VerticalGroup } from '@grafana/ui';
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { readHeatmapRowsCustomMeta } from 'app/features/transformers/calculateHeatmap/heatmap';
import { isHeatmapCellsDense, readHeatmapRowsCustomMeta } from 'app/features/transformers/calculateHeatmap/heatmap';
import { HeatmapCellLayout } from 'app/features/transformers/calculateHeatmap/models.gen';
import { DataHoverView } from '../geomap/components/DataHoverView';
@ -154,7 +154,11 @@ const HeatmapHoverCell = ({ data, hover, showHistogram }: Props) => {
[index]
);
if (data.heatmap?.meta?.type === DataFrameType.HeatmapSparse) {
const [isSparse] = useState(
() => data.heatmap?.meta?.type === DataFrameType.HeatmapCells && !isHeatmapCellsDense(data.heatmap)
);
if (isSparse) {
return (
<div>
<DataHoverView data={data.heatmap} rowIndex={index} />

@ -1,7 +1,7 @@
import { css } from '@emotion/css';
import React, { useCallback, useMemo, useRef, useState } from 'react';
import { DataFrameType, GrafanaTheme2, PanelProps, reduceField, ReducerID, TimeRange } from '@grafana/data';
import { DataFrameType, GrafanaTheme2, PanelProps, TimeRange } from '@grafana/data';
import { PanelDataErrorView } from '@grafana/runtime';
import { ScaleDistributionConfig } from '@grafana/schema';
import {
@ -16,7 +16,7 @@ import {
} from '@grafana/ui';
import { CloseButton } from 'app/core/components/CloseButton/CloseButton';
import { ColorScale } from 'app/core/components/ColorScale/ColorScale';
import { readHeatmapRowsCustomMeta } from 'app/features/transformers/calculateHeatmap/heatmap';
import { isHeatmapCellsDense, readHeatmapRowsCustomMeta } from 'app/features/transformers/calculateHeatmap/heatmap';
import { HeatmapHoverView } from './HeatmapHoverView';
import { prepareHeatmapData } from './fields';
@ -133,8 +133,6 @@ export const HeatmapPanel: React.FC<HeatmapPanelProps> = ({
cellGap: options.cellGap,
hideLE: options.filterValues?.le,
hideGE: options.filterValues?.ge,
valueMin: options.color.min,
valueMax: options.color.max,
exemplarColor: options.exemplars?.color ?? 'rgba(255,0,255,0.7)',
yAxisConfig: options.yAxis,
ySizeDivisor: scaleConfig?.type === ScaleDistribution.Log ? +(options.calculation?.yBuckets?.value || 1) : 1,
@ -148,21 +146,10 @@ export const HeatmapPanel: React.FC<HeatmapPanelProps> = ({
}
let heatmapType = dataRef.current?.heatmap?.meta?.type;
let countFieldIdx = heatmapType === DataFrameType.HeatmapCells ? 2 : 3;
let isSparseHeatmap = heatmapType === DataFrameType.HeatmapCells && !isHeatmapCellsDense(dataRef.current?.heatmap!);
let countFieldIdx = !isSparseHeatmap ? 2 : 3;
const countField = info.heatmap.fields[countFieldIdx];
// TODO -- better would be to get the range from the real color scale!
let { min, max } = options.color;
if (min == null || max == null) {
const calc = reduceField({ field: countField, reducers: [ReducerID.min, ReducerID.max] });
if (min == null) {
min = calc[ReducerID.min];
}
if (max == null) {
max = calc[ReducerID.max];
}
}
let hoverValue: number | undefined = undefined;
// seriesIdx: 1 is heatmap layer; 2 is exemplar layer
if (hover && info.heatmap.fields && hover.seriesIdx === 1) {
@ -172,7 +159,13 @@ export const HeatmapPanel: React.FC<HeatmapPanelProps> = ({
return (
<VizLayout.Legend placement="bottom" maxHeight="20%">
<div className={styles.colorScaleWrapper}>
<ColorScale hoverValue={hoverValue} colorPalette={palette} min={min!} max={max!} display={info.display} />
<ColorScale
hoverValue={hoverValue}
colorPalette={palette}
min={dataRef.current.minValue!}
max={dataRef.current.maxValue!}
display={info.display}
/>
</div>
</VizLayout.Legend>
);

@ -12,12 +12,14 @@ import {
} from '@grafana/data';
import {
calculateHeatmapFromData,
isHeatmapCellsDense,
readHeatmapRowsCustomMeta,
rowsToCellsHeatmap,
} from 'app/features/transformers/calculateHeatmap/heatmap';
import { HeatmapCellLayout } from 'app/features/transformers/calculateHeatmap/models.gen';
import { CellValues, PanelOptions } from './models.gen';
import { boundedMinMax } from './utils';
export interface HeatmapData {
heatmap?: DataFrame; // data we will render
@ -33,6 +35,10 @@ export interface HeatmapData {
xLayout?: HeatmapCellLayout;
yLayout?: HeatmapCellLayout;
// color scale range
minValue?: number;
maxValue?: number;
// Print a heatmap cell value
display?: (v: number) => string;
@ -49,18 +55,17 @@ export function prepareHeatmapData(data: PanelData, options: PanelOptions, theme
const exemplars = data.annotations?.find((f) => f.name === 'exemplar');
if (options.calculate) {
return getHeatmapData(calculateHeatmapFromData(frames, options.calculation ?? {}), exemplars, options, theme);
return getDenseHeatmapData(calculateHeatmapFromData(frames, options.calculation ?? {}), exemplars, options, theme);
}
// Check for known heatmap types
let rowsHeatmap: DataFrame | undefined = undefined;
for (const frame of frames) {
switch (frame.meta?.type) {
case DataFrameType.HeatmapSparse:
return getSparseHeatmapData(frame, exemplars, options, theme);
case DataFrameType.HeatmapCells:
return getHeatmapData(frame, exemplars, options, theme);
return isHeatmapCellsDense(frame)
? getDenseHeatmapData(frame, exemplars, options, theme)
: getSparseHeatmapData(frame, exemplars, options, theme);
case DataFrameType.HeatmapRows:
rowsHeatmap = frame; // the default format
@ -80,7 +85,7 @@ export function prepareHeatmapData(data: PanelData, options: PanelOptions, theme
}
}
return getHeatmapData(
return getDenseHeatmapData(
rowsToCellsHeatmap({
unit: options.yAxis?.unit, // used to format the ordinal lookup values
decimals: options.yAxis?.decimals,
@ -99,7 +104,7 @@ const getSparseHeatmapData = (
options: PanelOptions,
theme: GrafanaTheme2
): HeatmapData => {
if (frame.meta?.type !== DataFrameType.HeatmapSparse) {
if (frame.meta?.type !== DataFrameType.HeatmapCells || isHeatmapCellsDense(frame)) {
return {
warning: 'Expected sparse heatmap format',
heatmap: frame,
@ -112,14 +117,24 @@ const getSparseHeatmapData = (
// cell value display
const disp = updateFieldDisplay(frame.fields[3], options.cellValues, theme);
let [minValue, maxValue] = boundedMinMax(
frame.fields[3].values.toArray(),
options.color.min,
options.color.max,
options.filterValues?.le,
options.filterValues?.ge
);
return {
heatmap: frame,
minValue,
maxValue,
exemplars,
display: (v) => formattedValueToString(disp(v)),
};
};
const getHeatmapData = (
const getDenseHeatmapData = (
frame: DataFrame,
exemplars: DataFrame | undefined,
options: PanelOptions,
@ -201,6 +216,14 @@ const getHeatmapData = (
let yBinIncr = ys[1] - ys[0];
let xBinIncr = xs[yBinQty] - xs[0];
let [minValue, maxValue] = boundedMinMax(
valueField.values.toArray(),
options.color.min,
options.color.max,
options.filterValues?.le,
options.filterValues?.ge
);
const data: HeatmapData = {
heatmap: frame,
exemplars: exemplars?.length ? exemplars : undefined,
@ -209,6 +232,9 @@ const getHeatmapData = (
xBucketCount: xBinQty,
yBucketCount: yBinQty,
minValue,
maxValue,
// TODO: improve heuristic
xLayout:
xName === 'xMax' ? HeatmapCellLayout.le : xName === 'xMin' ? HeatmapCellLayout.ge : HeatmapCellLayout.unknown,

@ -17,7 +17,7 @@ import {
} from '@grafana/data';
import { AxisPlacement, ScaleDirection, ScaleDistribution, ScaleOrientation } from '@grafana/schema';
import { UPlotConfigBuilder } from '@grafana/ui';
import { readHeatmapRowsCustomMeta } from 'app/features/transformers/calculateHeatmap/heatmap';
import { isHeatmapCellsDense, readHeatmapRowsCustomMeta } from 'app/features/transformers/calculateHeatmap/heatmap';
import { HeatmapCellLayout } from 'app/features/transformers/calculateHeatmap/models.gen';
import { pointWithin, Quadtree, Rect } from '../barchart/quadtree';
@ -72,8 +72,6 @@ interface PrepConfigOpts {
cellGap?: number | null; // in css pixels
hideLE?: number;
hideGE?: number;
valueMin?: number;
valueMax?: number;
yAxisConfig: YAxisConfig;
ySizeDivisor?: number;
sync?: () => DashboardCursorSync;
@ -94,8 +92,6 @@ export function prepConfig(opts: PrepConfigOpts) {
cellGap,
hideLE,
hideGE,
valueMin,
valueMax,
yAxisConfig,
ySizeDivisor,
sync,
@ -264,7 +260,8 @@ export function prepConfig(opts: PrepConfigOpts) {
const yFieldConfig = yField.config?.custom as PanelFieldConfig | undefined;
const yScale = yFieldConfig?.scaleDistribution ?? { type: ScaleDistribution.Linear };
const yAxisReverse = Boolean(yAxisConfig.reverse);
const shouldUseLogScale = yScale.type !== ScaleDistribution.Linear || heatmapType === DataFrameType.HeatmapSparse;
const isSparseHeatmap = heatmapType === DataFrameType.HeatmapCells && !isHeatmapCellsDense(dataRef.current?.heatmap!);
const shouldUseLogScale = yScale.type !== ScaleDistribution.Linear || isSparseHeatmap;
const isOrdianalY = readHeatmapRowsCustomMeta(dataRef.current?.heatmap).yOrdinalDisplay != null;
// random to prevent syncing y in other heatmaps
@ -282,7 +279,7 @@ export function prepConfig(opts: PrepConfigOpts) {
log: yScale.log ?? 2,
range:
// sparse already accounts for le/ge by explicit yMin & yMax cell bounds, so no need to expand y range
heatmapType === DataFrameType.HeatmapSparse
isSparseHeatmap
? (u, dataMin, dataMax) => {
let scaleMin: number | null, scaleMax: number | null;
@ -432,7 +429,7 @@ export function prepConfig(opts: PrepConfigOpts) {
if (meta.yOrdinalDisplay) {
return splits.map((v) =>
v < 0
? meta.yZeroDisplay ?? '' // Check prometheus style labels
? meta.yMinDisplay ?? '' // Check prometheus style labels
: meta.yOrdinalDisplay[v] ?? ''
);
}
@ -441,7 +438,7 @@ export function prepConfig(opts: PrepConfigOpts) {
: undefined,
});
const pathBuilder = heatmapType === DataFrameType.HeatmapCells ? heatmapPathsDense : heatmapPathsSparse;
const pathBuilder = isSparseHeatmap ? heatmapPathsSparse : heatmapPathsDense;
// heatmap layer
builder.addSeries({
@ -485,8 +482,13 @@ export function prepConfig(opts: PrepConfigOpts) {
disp: {
fill: {
values: (u, seriesIdx) => {
let countFacetIdx = heatmapType === DataFrameType.HeatmapCells ? 2 : 3;
return valuesToFills(u.data[seriesIdx][countFacetIdx] as unknown as number[], palette, valueMin, valueMax);
let countFacetIdx = !isSparseHeatmap ? 2 : 3;
return valuesToFills(
u.data[seriesIdx][countFacetIdx] as unknown as number[],
palette,
dataRef.current?.minValue!,
dataRef.current?.maxValue!
);
},
index: palette,
},
@ -886,23 +888,37 @@ export function heatmapPathsSparse(opts: PathbuilderOpts) {
};
}
export const valuesToFills = (values: number[], palette: string[], minValue?: number, maxValue?: number) => {
export const boundedMinMax = (
values: number[],
minValue?: number,
maxValue?: number,
hideLE = -Infinity,
hideGE = Infinity
) => {
if (minValue == null) {
minValue = Infinity;
for (let i = 0; i < values.length; i++) {
if (values[i] > hideLE && values[i] < hideGE) {
minValue = Math.min(minValue, values[i]);
}
}
}
if (maxValue == null) {
maxValue = -Infinity;
for (let i = 0; i < values.length; i++) {
if (values[i] > hideLE && values[i] < hideGE) {
maxValue = Math.max(maxValue, values[i]);
}
}
}
return [minValue, maxValue];
};
export const valuesToFills = (values: number[], palette: string[], minValue: number, maxValue: number) => {
let range = maxValue - minValue;
let paletteSize = palette.length;

Loading…
Cancel
Save