NewGauge: Adds scale labels (threshold labels) (#112517)

* Progress on scale label

* Progress

* Progress on labels

* Progress

* fix for segments

* Update

* Fix

* Update

* Some basic unit tests

* fixes

* fixes
This commit is contained in:
Torkel Ödegaard
2025-10-17 13:51:54 +02:00
committed by GitHub
parent cfba349fdb
commit e014f1ba0c
18 changed files with 922 additions and 119 deletions
@@ -91,6 +91,7 @@
"segmentCount": 1,
"segmentSpacing": 0.3,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false
},
@@ -171,6 +172,7 @@
"segmentCount": 1,
"segmentSpacing": 0.3,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false
},
@@ -251,6 +253,7 @@
"segmentCount": 1,
"segmentSpacing": 0.3,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false
},
@@ -331,6 +334,7 @@
"segmentCount": 1,
"segmentSpacing": 0.3,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false
},
@@ -411,6 +415,7 @@
"segmentCount": 1,
"segmentSpacing": 0.3,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": true
},
@@ -491,6 +496,7 @@
"segmentCount": 1,
"segmentSpacing": 0.3,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false
},
@@ -571,6 +577,7 @@
"segmentCount": 1,
"segmentSpacing": 0.3,
"shape": "gauge",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false
},
@@ -664,6 +671,7 @@
"segmentCount": 1,
"segmentSpacing": 0.3,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false
},
@@ -744,6 +752,7 @@
"segmentCount": 1,
"segmentSpacing": 0.3,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false
},
@@ -824,6 +833,7 @@
"segmentCount": 1,
"segmentSpacing": 0.3,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false
},
@@ -904,6 +914,7 @@
"segmentCount": 1,
"segmentSpacing": 0.3,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false
},
@@ -1001,6 +1012,7 @@
"segmentCount": 1,
"segmentSpacing": 0.3,
"shape": "gauge",
"showThresholdLabels": false,
"showThresholdMarkers": true,
"sparkline": false
},
@@ -1079,8 +1091,9 @@
"values": false
},
"segmentCount": 37,
"segmentSpacing": 0.19,
"segmentSpacing": 0.31,
"shape": "gauge",
"showThresholdLabels": false,
"showThresholdMarkers": true,
"sparkline": false
},
@@ -1159,8 +1172,9 @@
"values": false
},
"segmentCount": 46,
"segmentSpacing": 0.19,
"segmentSpacing": 0.3,
"shape": "gauge",
"showThresholdLabels": false,
"showThresholdMarkers": true,
"sparkline": false
},
@@ -1219,10 +1233,10 @@
"x": 0,
"y": 27
},
"id": 27,
"id": 30,
"maxDataPoints": 20,
"options": {
"barWidthFactor": 0.43,
"barWidthFactor": 0.9,
"effects": {
"barGlow": false,
"centerGlow": false,
@@ -1239,8 +1253,9 @@
"values": false
},
"segmentCount": 1,
"segmentSpacing": 0.19,
"shape": "circle",
"segmentSpacing": 0.3,
"shape": "gauge",
"showThresholdLabels": false,
"showThresholdMarkers": true,
"sparkline": false
},
@@ -1257,7 +1272,7 @@
"stringInput": "1,20,90,30,5,70"
}
],
"title": "Thresholds circle",
"title": "Plain thresholds",
"type": "radialbar"
},
{
@@ -1319,8 +1334,9 @@
"values": false
},
"segmentCount": 62,
"segmentSpacing": 0.19,
"segmentSpacing": 0.3,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": true,
"sparkline": false
},
@@ -1348,6 +1364,274 @@
"x": 0,
"y": 34
},
"id": 31,
"panels": [],
"title": "Threshold labels",
"type": "row"
},
{
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"max": 100,
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "semi-dark-yellow",
"value": 40
},
{
"color": "orange",
"value": 65
},
{
"color": "red",
"value": 85
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 10,
"w": 7,
"x": 0,
"y": 35
},
"id": 32,
"maxDataPoints": 20,
"options": {
"barWidthFactor": 0.9,
"effects": {
"barGlow": false,
"centerGlow": false,
"rounded": false,
"spotlight": false
},
"gradient": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"segmentCount": 62,
"segmentSpacing": 0.3,
"shape": "gauge",
"showThresholdLabels": true,
"showThresholdMarkers": true,
"sparkline": false
},
"pluginVersion": "12.2.0-pre",
"targets": [
{
"alias": "1",
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"refId": "A",
"scenarioId": "csv_metric_values",
"stringInput": "1,20,90,30,5,70"
}
],
"title": "Thresholds labels",
"type": "radialbar"
},
{
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"max": 100,
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "semi-dark-yellow",
"value": 40
},
{
"color": "orange",
"value": 65
},
{
"color": "red",
"value": 85
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 10,
"w": 7,
"x": 7,
"y": 35
},
"id": 34,
"maxDataPoints": 20,
"options": {
"barWidthFactor": 0.9,
"effects": {
"barGlow": false,
"centerGlow": false,
"rounded": false,
"spotlight": false
},
"gradient": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"segmentCount": 62,
"segmentSpacing": 0.3,
"shape": "gauge",
"showThresholdLabels": true,
"showThresholdMarkers": false,
"sparkline": false
},
"pluginVersion": "12.2.0-pre",
"targets": [
{
"alias": "1",
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"refId": "A",
"scenarioId": "csv_metric_values",
"stringInput": "1,20,90,30,5,70"
}
],
"title": "Thresholds labels only",
"type": "radialbar"
},
{
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"max": 100,
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "semi-dark-yellow",
"value": 40
},
{
"color": "orange",
"value": 65
},
{
"color": "red",
"value": 85
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 10,
"w": 6,
"x": 14,
"y": 35
},
"id": 33,
"maxDataPoints": 20,
"options": {
"barWidthFactor": 0.9,
"effects": {
"barGlow": false,
"centerGlow": false,
"rounded": false,
"spotlight": false
},
"gradient": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"segmentCount": 80,
"segmentSpacing": 0.59,
"shape": "circle",
"showThresholdLabels": true,
"showThresholdMarkers": true,
"sparkline": false
},
"pluginVersion": "12.2.0-pre",
"targets": [
{
"alias": "1",
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"refId": "A",
"scenarioId": "csv_metric_values",
"stringInput": "1,20,90,30,5,70"
}
],
"title": "Thresholds labels + circle",
"type": "radialbar"
},
{
"collapsed": false,
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 45
},
"id": 6,
"panels": [],
"title": "Gradient modes",
@@ -1389,7 +1673,7 @@
"h": 6,
"w": 24,
"x": 0,
"y": 35
"y": 46
},
"id": 9,
"maxDataPoints": 20,
@@ -1415,6 +1699,7 @@
"segmentCount": 1,
"segmentSpacing": 0.3,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false,
"spotlight": true
@@ -1476,7 +1761,7 @@
"h": 6,
"w": 24,
"x": 0,
"y": 41
"y": 52
},
"id": 11,
"maxDataPoints": 20,
@@ -1502,6 +1787,7 @@
"segmentCount": 1,
"segmentSpacing": 0.3,
"shape": "gauge",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": true,
"spotlight": true
@@ -1533,7 +1819,7 @@
"h": 1,
"w": 24,
"x": 0,
"y": 47
"y": 58
},
"id": 12,
"panels": [],
@@ -1577,7 +1863,7 @@
"h": 7,
"w": 4,
"x": 0,
"y": 48
"y": 59
},
"id": 13,
"maxDataPoints": 20,
@@ -1603,6 +1889,7 @@
"segmentCount": 8,
"segmentSpacing": 0.3,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false,
"spotlight": true
@@ -1665,7 +1952,7 @@
"h": 7,
"w": 5,
"x": 4,
"y": 48
"y": 59
},
"id": 14,
"maxDataPoints": 20,
@@ -1692,6 +1979,7 @@
"segmentCount": 12,
"segmentSpacing": 0.3,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false,
"spotlight": true
@@ -1753,7 +2041,7 @@
"h": 7,
"w": 5,
"x": 9,
"y": 48
"y": 59
},
"id": 15,
"maxDataPoints": 20,
@@ -1779,6 +2067,7 @@
"segmentCount": 39,
"segmentSpacing": 0.76,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false,
"spotlight": true
@@ -1840,7 +2129,7 @@
"h": 7,
"w": 6,
"x": 14,
"y": 48
"y": 59
},
"id": 16,
"maxDataPoints": 20,
@@ -1866,6 +2155,7 @@
"segmentCount": 81,
"segmentSpacing": 0.28,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false,
"spotlight": true
@@ -89,6 +89,7 @@
"segmentCount": 1,
"segmentSpacing": 0.3,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false
},
@@ -167,6 +168,7 @@
"segmentCount": 1,
"segmentSpacing": 0.3,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false
},
@@ -245,6 +247,7 @@
"segmentCount": 1,
"segmentSpacing": 0.3,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false
},
@@ -323,6 +326,7 @@
"segmentCount": 1,
"segmentSpacing": 0.3,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false
},
@@ -401,6 +405,7 @@
"segmentCount": 1,
"segmentSpacing": 0.3,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": true
},
@@ -479,6 +484,7 @@
"segmentCount": 1,
"segmentSpacing": 0.3,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false
},
@@ -557,6 +563,7 @@
"segmentCount": 1,
"segmentSpacing": 0.3,
"shape": "gauge",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false
},
@@ -648,6 +655,7 @@
"segmentCount": 1,
"segmentSpacing": 0.3,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false
},
@@ -726,6 +734,7 @@
"segmentCount": 1,
"segmentSpacing": 0.3,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false
},
@@ -804,6 +813,7 @@
"segmentCount": 1,
"segmentSpacing": 0.3,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false
},
@@ -882,6 +892,7 @@
"segmentCount": 1,
"segmentSpacing": 0.3,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false
},
@@ -977,6 +988,7 @@
"segmentCount": 1,
"segmentSpacing": 0.3,
"shape": "gauge",
"showThresholdLabels": false,
"showThresholdMarkers": true,
"sparkline": false
},
@@ -1053,8 +1065,9 @@
"values": false
},
"segmentCount": 37,
"segmentSpacing": 0.19,
"segmentSpacing": 0.31,
"shape": "gauge",
"showThresholdLabels": false,
"showThresholdMarkers": true,
"sparkline": false
},
@@ -1131,8 +1144,9 @@
"values": false
},
"segmentCount": 46,
"segmentSpacing": 0.19,
"segmentSpacing": 0.3,
"shape": "gauge",
"showThresholdLabels": false,
"showThresholdMarkers": true,
"sparkline": false
},
@@ -1191,10 +1205,10 @@
"x": 0,
"y": 27
},
"id": 27,
"id": 30,
"maxDataPoints": 20,
"options": {
"barWidthFactor": 0.43,
"barWidthFactor": 0.9,
"effects": {
"barGlow": false,
"centerGlow": false,
@@ -1209,8 +1223,9 @@
"values": false
},
"segmentCount": 1,
"segmentSpacing": 0.19,
"shape": "circle",
"segmentSpacing": 0.3,
"shape": "gauge",
"showThresholdLabels": false,
"showThresholdMarkers": true,
"sparkline": false
},
@@ -1227,7 +1242,7 @@
"stringInput": "1,20,90,30,5,70"
}
],
"title": "Thresholds circle",
"title": "Plain thresholds",
"type": "radialbar"
},
{
@@ -1287,8 +1302,9 @@
"values": false
},
"segmentCount": 62,
"segmentSpacing": 0.19,
"segmentSpacing": 0.3,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": true,
"sparkline": false
},
@@ -1316,6 +1332,268 @@
"x": 0,
"y": 34
},
"id": 31,
"panels": [],
"title": "Threshold labels",
"type": "row"
},
{
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"max": 100,
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "semi-dark-yellow",
"value": 40
},
{
"color": "orange",
"value": 65
},
{
"color": "red",
"value": 85
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 10,
"w": 7,
"x": 0,
"y": 35
},
"id": 32,
"maxDataPoints": 20,
"options": {
"barWidthFactor": 0.9,
"effects": {
"barGlow": false,
"centerGlow": false,
"rounded": false,
"spotlight": false
},
"gradient": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": ["lastNotNull"],
"fields": "",
"values": false
},
"segmentCount": 62,
"segmentSpacing": 0.3,
"shape": "gauge",
"showThresholdLabels": true,
"showThresholdMarkers": true,
"sparkline": false
},
"pluginVersion": "12.2.0-pre",
"targets": [
{
"alias": "1",
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"refId": "A",
"scenarioId": "csv_metric_values",
"stringInput": "1,20,90,30,5,70"
}
],
"title": "Thresholds labels",
"type": "radialbar"
},
{
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"max": 100,
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "semi-dark-yellow",
"value": 40
},
{
"color": "orange",
"value": 65
},
{
"color": "red",
"value": 85
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 10,
"w": 7,
"x": 7,
"y": 35
},
"id": 34,
"maxDataPoints": 20,
"options": {
"barWidthFactor": 0.9,
"effects": {
"barGlow": false,
"centerGlow": false,
"rounded": false,
"spotlight": false
},
"gradient": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": ["lastNotNull"],
"fields": "",
"values": false
},
"segmentCount": 62,
"segmentSpacing": 0.3,
"shape": "gauge",
"showThresholdLabels": true,
"showThresholdMarkers": false,
"sparkline": false
},
"pluginVersion": "12.2.0-pre",
"targets": [
{
"alias": "1",
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"refId": "A",
"scenarioId": "csv_metric_values",
"stringInput": "1,20,90,30,5,70"
}
],
"title": "Thresholds labels only",
"type": "radialbar"
},
{
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"max": 100,
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "semi-dark-yellow",
"value": 40
},
{
"color": "orange",
"value": 65
},
{
"color": "red",
"value": 85
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 10,
"w": 6,
"x": 14,
"y": 35
},
"id": 33,
"maxDataPoints": 20,
"options": {
"barWidthFactor": 0.9,
"effects": {
"barGlow": false,
"centerGlow": false,
"rounded": false,
"spotlight": false
},
"gradient": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": ["lastNotNull"],
"fields": "",
"values": false
},
"segmentCount": 80,
"segmentSpacing": 0.59,
"shape": "circle",
"showThresholdLabels": true,
"showThresholdMarkers": true,
"sparkline": false
},
"pluginVersion": "12.2.0-pre",
"targets": [
{
"alias": "1",
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"refId": "A",
"scenarioId": "csv_metric_values",
"stringInput": "1,20,90,30,5,70"
}
],
"title": "Thresholds labels + circle",
"type": "radialbar"
},
{
"collapsed": false,
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 45
},
"id": 6,
"panels": [],
"title": "Gradient modes",
@@ -1357,7 +1635,7 @@
"h": 6,
"w": 24,
"x": 0,
"y": 35
"y": 46
},
"id": 9,
"maxDataPoints": 20,
@@ -1381,6 +1659,7 @@
"segmentCount": 1,
"segmentSpacing": 0.3,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false,
"spotlight": true
@@ -1442,7 +1721,7 @@
"h": 6,
"w": 24,
"x": 0,
"y": 41
"y": 52
},
"id": 11,
"maxDataPoints": 20,
@@ -1466,6 +1745,7 @@
"segmentCount": 1,
"segmentSpacing": 0.3,
"shape": "gauge",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": true,
"spotlight": true
@@ -1497,7 +1777,7 @@
"h": 1,
"w": 24,
"x": 0,
"y": 47
"y": 58
},
"id": 12,
"panels": [],
@@ -1541,7 +1821,7 @@
"h": 7,
"w": 4,
"x": 0,
"y": 48
"y": 59
},
"id": 13,
"maxDataPoints": 20,
@@ -1565,6 +1845,7 @@
"segmentCount": 8,
"segmentSpacing": 0.3,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false,
"spotlight": true
@@ -1627,7 +1908,7 @@
"h": 7,
"w": 5,
"x": 4,
"y": 48
"y": 59
},
"id": 14,
"maxDataPoints": 20,
@@ -1652,6 +1933,7 @@
"segmentCount": 12,
"segmentSpacing": 0.3,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false,
"spotlight": true
@@ -1713,7 +1995,7 @@
"h": 7,
"w": 5,
"x": 9,
"y": 48
"y": 59
},
"id": 15,
"maxDataPoints": 20,
@@ -1737,6 +2019,7 @@
"segmentCount": 39,
"segmentSpacing": 0.76,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false,
"spotlight": true
@@ -1798,7 +2081,7 @@
"h": 7,
"w": 6,
"x": 14,
"y": 48
"y": 59
},
"id": 16,
"maxDataPoints": 20,
@@ -1822,6 +2105,7 @@
"segmentCount": 81,
"segmentSpacing": 0.28,
"shape": "circle",
"showThresholdLabels": false,
"showThresholdMarkers": false,
"sparkline": false,
"spotlight": true
@@ -1862,5 +2146,5 @@
"timezone": "browser",
"title": "Panel tests - Gauge (new)",
"uid": "panel-tests-gauge-new",
"version": 1
"version": 6
}
@@ -33,6 +33,7 @@ export interface Options extends common.SingleStatBaseOptions {
segmentCount: number;
segmentSpacing: number;
shape: ('circle' | 'gauge');
showThresholdLabels: boolean;
showThresholdMarkers: boolean;
sparkline?: boolean;
}
@@ -44,6 +45,7 @@ export const defaultOptions: Partial<Options> = {
segmentCount: 1,
segmentSpacing: 0.3,
shape: 'gauge',
showThresholdLabels: false,
showThresholdMarkers: true,
sparkline: false,
};
@@ -484,6 +484,7 @@ const getStyles = (theme: GrafanaTheme2) => {
height: '100%',
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
'.always-show': {
background: 'none',
@@ -40,7 +40,7 @@ export function RadialBarSegmented({
const angleValue = ((max - min) / segmentCountAdjusted) * i;
const angleColor = colorDefs.getSegmentColor(angleValue);
const segmentAngle = startAngle + (angleRange / segmentCountAdjusted) * i + 0.01;
const segmentColor = angleValue > value ? theme.colors.action.hover : angleColor;
const segmentColor = angleValue >= value ? theme.colors.action.hover : angleColor;
segments.push(
<RadialArcPath
@@ -65,7 +65,7 @@ export function RadialBarSegmented({
export function getAngleBetweenSegments(segmentSpacing: number, segmentCount: number, range: number) {
// Max spacing is 8 degrees between segments
// Changing this constant could be considered a breaking change
const maxAngleBetweenSegments = Math.max(range / 4 / segmentCount, 2);
const maxAngleBetweenSegments = Math.max(range / 1.5 / segmentCount, 2);
return segmentSpacing * maxAngleBetweenSegments;
}
@@ -15,6 +15,7 @@ export interface RadialColorDefsOptions {
displayProcessor: DisplayProcessor;
}
// eslint-disable-next-line react-prefer-function-component/react-prefer-function-component
export class RadialColorDefs {
private colorToIds: Record<string, string> = {};
private defs: React.ReactNode[] = [];
@@ -45,7 +45,7 @@ const meta: Meta<StoryProps> = {
gradient: 'none',
seriesCount: 1,
segmentCount: 0,
segmentSpacing: 0.4,
segmentSpacing: 0.2,
roundedBars: false,
thresholdsBar: false,
colorScheme: FieldColorModeId.Thresholds,
@@ -97,28 +97,69 @@ export const Basic: StoryFn<StoryProps> = (args) => {
);
};
export const Examples: StoryFn = (args) => {
export const Examples: StoryFn<StoryProps> = (args) => {
return (
<Stack direction={'column'} gap={3} wrap="wrap">
<div>Bar width</div>
<Stack direction="row" alignItems="center" gap={3} wrap="wrap">
<RadialGaugeExample seriesName="0.1" value={60} color="blue" barWidthFactor={0.1} />
<RadialGaugeExample seriesName="0.4" value={60} color="green" barWidthFactor={0.4} />
<RadialGaugeExample seriesName="0.6" value={60} color="red" barWidthFactor={0.6} />
<RadialGaugeExample seriesName="0.8" value={60} color="purple" barWidthFactor={0.8} />
<RadialGaugeExample
seriesName="0.1"
value={args.value ?? 30}
color="blue"
gradient="auto"
barWidthFactor={0.1}
/>
<RadialGaugeExample
seriesName="0.4"
value={args.value ?? 40}
color="green"
gradient="auto"
barWidthFactor={0.4}
/>
<RadialGaugeExample
seriesName="0.6"
value={args.value ?? 60}
color="red"
gradient="auto"
barWidthFactor={0.6}
/>
<RadialGaugeExample
seriesName="0.8"
value={args.value ?? 70}
color="purple"
gradient="auto"
barWidthFactor={0.8}
/>
</Stack>
<div>Effects</div>
<Stack direction="row" alignItems="center" gap={3} wrap="wrap">
<RadialGaugeExample value={30} spotlight glowBar glowCenter color="blue" gradient="auto" />
<RadialGaugeExample value={40} spotlight glowBar glowCenter color="green" gradient="auto" />
<RadialGaugeExample value={60} spotlight glowBar glowCenter color="red" gradient="auto" roundedBars />
<RadialGaugeExample value={70} spotlight glowBar glowCenter color="purple" gradient="auto" roundedBars />
<RadialGaugeExample value={args.value ?? 30} spotlight glowBar glowCenter color="blue" gradient="auto" />
<RadialGaugeExample value={args.value ?? 40} spotlight glowBar glowCenter color="green" gradient="auto" />
<RadialGaugeExample
value={args.value ?? 60}
spotlight
glowBar
glowCenter
color="red"
gradient="auto"
roundedBars
/>
<RadialGaugeExample
value={args.value ?? 70}
spotlight
glowBar
glowCenter
color="purple"
gradient="auto"
roundedBars
/>
</Stack>
<div>Shape: Gauge & color scale</div>
<Stack direction="row" alignItems="center" gap={3} wrap="wrap">
<RadialGaugeExample
value={40}
shape="gauge"
width={250}
gradient="auto"
colorScheme={FieldColorModeId.ContinuousGrYlRd}
glowCenter={true}
@@ -127,6 +168,7 @@ export const Examples: StoryFn = (args) => {
<RadialGaugeExample
colorScheme={FieldColorModeId.ContinuousGrYlRd}
gradient="auto"
width={250}
value={90}
barWidthFactor={0.6}
roundedBars={false}
@@ -138,10 +180,9 @@ export const Examples: StoryFn = (args) => {
<div>Sparklines</div>
<Stack direction={'row'} gap={3}>
<RadialGaugeExample
value={70}
value={args.value ?? 70}
color="blue"
shape="gauge"
{...args}
gradient="auto"
sparkline={true}
spotlight
@@ -150,10 +191,9 @@ export const Examples: StoryFn = (args) => {
barWidthFactor={0.2}
/>
<RadialGaugeExample
value={30}
value={args.value ?? 30}
color="green"
shape="gauge"
{...args}
gradient="auto"
sparkline={true}
spotlight
@@ -162,9 +202,8 @@ export const Examples: StoryFn = (args) => {
barWidthFactor={0.8}
/>
<RadialGaugeExample
value={50}
value={args.value ?? 50}
color="red"
{...args}
shape="gauge"
width={250}
gradient="auto"
@@ -175,9 +214,8 @@ export const Examples: StoryFn = (args) => {
barWidthFactor={0.2}
/>
<RadialGaugeExample
value={50}
value={args.value ?? 50}
color="red"
{...args}
width={250}
shape="gauge"
gradient="auto"
@@ -191,39 +229,36 @@ export const Examples: StoryFn = (args) => {
<div>Segmented</div>
<Stack direction={'row'} gap={3}>
<RadialGaugeExample
value={70}
value={args.value ?? 70}
color="green"
{...args}
gradient="auto"
glowCenter={true}
segmentCount={8}
segmentSpacing={0.1}
barWidthFactor={0.4}
/>
<RadialGaugeExample
value={30}
color="green"
{...args}
value={args.value ?? 30}
color="purple"
gradient="auto"
segmentCount={20}
segmentCount={30}
glowCenter={true}
barWidthFactor={0.5}
barWidthFactor={0.6}
/>
<RadialGaugeExample
value={50}
value={args.value ?? 50}
color="red"
{...args}
gradient="auto"
segmentCount={40}
glowCenter={true}
barWidthFactor={0.7}
segmentSpacing={0.4}
barWidthFactor={1}
segmentSpacing={0.6}
/>
</Stack>
<div>Segmented color scale</div>
<Stack direction={'row'} gap={3}>
<RadialGaugeExample
value={70}
{...args}
value={args.value ?? 80}
colorScheme={FieldColorModeId.ContinuousGrYlRd}
spotlight
glowBar={true}
@@ -232,24 +267,25 @@ export const Examples: StoryFn = (args) => {
barWidthFactor={0.4}
/>
<RadialGaugeExample
value={70}
{...args}
value={args.value ?? 80}
width={250}
colorScheme={FieldColorModeId.ContinuousGrYlRd}
spotlight
shape="gauge"
gradient="auto"
glowBar={true}
glowCenter={true}
segmentCount={20}
barWidthFactor={0.4}
segmentCount={40}
segmentSpacing={0.5}
barWidthFactor={0.8}
/>
</Stack>
<div>Thresholds</div>
<Stack direction={'row'} gap={3}>
<RadialGaugeExample
value={70}
{...args}
value={args.value ?? 70}
colorScheme={FieldColorModeId.Thresholds}
gradient="auto"
thresholdsBar={true}
roundedBars={false}
spotlight
@@ -257,10 +293,10 @@ export const Examples: StoryFn = (args) => {
barWidthFactor={0.7}
/>
<RadialGaugeExample
value={70}
{...args}
value={args.value ?? 70}
width={250}
colorScheme={FieldColorModeId.Thresholds}
gradient="auto"
glowCenter={true}
thresholdsBar={true}
roundedBars={false}
@@ -268,14 +304,15 @@ export const Examples: StoryFn = (args) => {
barWidthFactor={0.7}
/>
<RadialGaugeExample
value={70}
{...args}
value={args.value ?? 70}
width={250}
colorScheme={FieldColorModeId.Thresholds}
gradient="auto"
glowCenter={true}
thresholdsBar={true}
roundedBars={false}
segmentCount={40}
segmentSpacing={0.2}
segmentSpacing={0.3}
shape="gauge"
barWidthFactor={0.7}
/>
@@ -340,6 +377,7 @@ interface ExampleProps {
thresholdsBar?: boolean;
colorScheme?: FieldColorModeId;
decimals?: number;
showScaleLabels?: boolean;
}
export function RadialGaugeExample({
@@ -366,6 +404,7 @@ export function RadialGaugeExample({
thresholdsBar = false,
colorScheme = FieldColorModeId.Thresholds,
decimals = 0,
showScaleLabels,
}: ExampleProps) {
const theme = useTheme2();
@@ -450,6 +489,7 @@ export function RadialGaugeExample({
segmentSpacing={segmentSpacing}
roundedBars={roundedBars}
thresholdsBar={thresholdsBar}
showScaleLabels={showScaleLabels}
/>
);
}
@@ -8,4 +8,11 @@ describe('RadialGauge', () => {
expect(screen.getByRole('img')).toBeInTheDocument();
});
it('should render threshold labels', () => {
render(<RadialGaugeExample showScaleLabels={true} />);
expect(screen.getByRole('img')).toBeInTheDocument();
expect(screen.getByLabelText('Threshold 85')).toBeInTheDocument();
});
});
@@ -6,10 +6,12 @@ import { DisplayValueAlignmentFactors, FieldDisplay, getDisplayProcessor, Grafan
import { t } from '@grafana/i18n';
import { useStyles2, useTheme2 } from '../../themes/ThemeContext';
import { getFormattedThresholds } from '../Gauge/utils';
import { RadialBar } from './RadialBar';
import { RadialBarSegmented } from './RadialBarSegmented';
import { RadialColorDefs } from './RadialColorDefs';
import { RadialScaleLabels } from './RadialScaleLabels';
import { RadialSparkline } from './RadialSparkline';
import { RadialText } from './RadialText';
import { ThresholdsBar } from './ThresholdsBar';
@@ -61,6 +63,7 @@ export interface RadialGaugeProps {
nameManualFontSize?: number;
/** Specify which text should be visible */
textMode?: RadialTextMode;
showScaleLabels?: boolean;
}
export type RadialGradientMode = 'none' | 'auto';
@@ -83,6 +86,7 @@ export function RadialGauge(props: RadialGaugeProps) {
segmentSpacing = 0.1,
roundedBars = true,
thresholdsBar = false,
showScaleLabels = false,
values,
} = props;
const theme = useTheme2();
@@ -108,7 +112,8 @@ export function RadialGauge(props: RadialGaugeProps) {
roundedBars,
barWidthFactor,
barIndex,
thresholdsBar
thresholdsBar,
showScaleLabels
);
const displayProcessor = getFieldDisplayProcessor(displayValue);
@@ -177,22 +182,6 @@ export function RadialGauge(props: RadialGaugeProps) {
graphics.push(<MiddleCircleGlow key="center-glow" gaugeId={gaugeId} color={color} dimensions={dimensions} />);
}
if (thresholdsBar) {
graphics.push(
<ThresholdsBar
key="thresholds-bar"
dimensions={dimensions}
fieldDisplay={displayValue}
startAngle={startAngle}
endAngle={endAngle}
angleRange={angleRange}
roundedBars={roundedBars}
glowFilter={`url(#${glowFilterId})`}
colorDefs={colorDefs}
/>
);
}
graphics.push(
<RadialText
key="radial-text"
@@ -207,6 +196,43 @@ export function RadialGauge(props: RadialGaugeProps) {
/>
);
if (showScaleLabels || thresholdsBar) {
const decimals = displayValue.field.decimals ?? 2;
const thresholds = getFormattedThresholds(decimals, displayValue.field, theme);
if (showScaleLabels) {
graphics.push(
<RadialScaleLabels
key="radial-scale-labels"
thresholds={thresholds}
fieldDisplay={displayValue}
angleRange={angleRange}
theme={theme}
dimensions={dimensions}
startAngle={startAngle}
endAngle={endAngle}
/>
);
}
if (thresholdsBar) {
graphics.push(
<ThresholdsBar
key="thresholds-bar"
thresholds={thresholds}
dimensions={dimensions}
fieldDisplay={displayValue}
startAngle={startAngle}
endAngle={endAngle}
angleRange={angleRange}
roundedBars={roundedBars}
glowFilter={`url(#${glowFilterId})`}
colorDefs={colorDefs}
/>
);
}
}
if (displayValue.sparkline) {
sparklineElement = (
<RadialSparkline
@@ -0,0 +1,87 @@
import { FieldDisplay, GrafanaTheme2, Threshold } from '@grafana/data';
import { t } from '@grafana/i18n';
import { measureText } from '../../utils/measureText';
import { GaugeDimensions, toCartesian } from './utils';
interface RadialScaleLabelsProps {
fieldDisplay: FieldDisplay;
theme: GrafanaTheme2;
thresholds: Threshold[];
dimensions: GaugeDimensions;
startAngle: number;
endAngle: number;
angleRange: number;
}
export function RadialScaleLabels({
fieldDisplay,
thresholds,
theme,
dimensions,
startAngle,
endAngle,
angleRange,
}: RadialScaleLabelsProps) {
const { centerX, centerY, scaleLabelsFontSize, scaleLabelsRadius } = dimensions;
const fieldConfig = fieldDisplay.field;
const min = fieldConfig.min ?? 0;
const max = fieldConfig.max ?? 100;
const fontSize = scaleLabelsFontSize;
const textLineHeight = scaleLabelsFontSize * 1.2;
const radius = scaleLabelsRadius - textLineHeight;
function getTextPosition(text: string, value: number, index: number) {
const isLast = index === thresholds.length - 1;
const isFirst = index === 0;
let valueDeg = ((value - min) / (max - min)) * angleRange;
let finalAngle = startAngle + valueDeg;
// Now adjust the final angle based on the label text width and the labels position on the arc
let measure = measureText(text, fontSize, theme.typography.fontWeightMedium);
let textWidthAngle = (measure.width / (2 * Math.PI * radius)) * angleRange;
// the centering is different for gauge or circle shapes for some reason
finalAngle -= endAngle < 180 ? textWidthAngle : textWidthAngle / 2;
// For circle gauges we need to shift the first label more
if (isFirst) {
finalAngle += textWidthAngle;
}
// For circle gauges we need to shift the last label more
if (isLast && endAngle === 360) {
finalAngle -= textWidthAngle;
}
const position = toCartesian(centerX, centerY, radius, finalAngle);
return { ...position, transform: `rotate(${finalAngle}, ${position.x}, ${position.y})` };
}
return (
<g>
{thresholds.map((threshold, index) => {
const labelPos = getTextPosition(String(threshold.value), threshold.value, index);
return (
<text
key={index}
x={labelPos.x}
y={labelPos.y}
fontSize={fontSize}
fill={theme.colors.text.primary}
transform={labelPos.transform}
aria-label={t(`gauge.threshold`, 'Threshold {{value}}', { value: threshold.value })}
>
{threshold.value}
</text>
);
})}
</g>
);
}
@@ -1,7 +1,4 @@
import { FieldDisplay } from '@grafana/data';
import { useTheme2 } from '../../themes/ThemeContext';
import { getFormattedThresholds } from '../Gauge/utils';
import { FieldDisplay, Threshold } from '@grafana/data';
import { RadialArcPath } from './RadialArcPath';
import { RadialColorDefs } from './RadialColorDefs';
@@ -16,6 +13,7 @@ export interface Props {
roundedBars?: boolean;
glowFilter?: string;
colorDefs: RadialColorDefs;
thresholds: Threshold[];
}
export function ThresholdsBar({
dimensions,
@@ -25,19 +23,16 @@ export function ThresholdsBar({
roundedBars,
glowFilter,
colorDefs,
thresholds,
}: Props) {
const theme = useTheme2();
const fieldConfig = fieldDisplay.field;
const decimals = fieldConfig.decimals ?? 2;
const min = fieldConfig.min ?? 0;
const max = fieldConfig.max ?? 100;
const thresholds = getFormattedThresholds(decimals, fieldConfig, theme);
const outerRadius = dimensions.radius + dimensions.barWidth / 2;
const thresholdDimensions = {
...dimensions,
barWidth: dimensions.thresholdsBarWidth,
radius: outerRadius + dimensions.thresholdsBarWidth / 2 + dimensions.thresholdsBarSpacing,
radius: dimensions.thresholdsBarRadius,
};
let currentStart = startAngle;
@@ -13,7 +13,8 @@ describe('RadialGauge utils', () => {
overrides.roundedBars ?? false,
overrides.barWidthFactor ?? 0.4,
overrides.barIndex ?? 0,
overrides.thresholdsBar ?? false
overrides.thresholdsBar ?? false,
overrides.showScaleLabels ?? false
);
}
@@ -99,6 +100,14 @@ describe('RadialGauge utils', () => {
expect(gauge.radius).toBeCloseTo(93.33, 1);
expect(gauge.centerY).toBeCloseTo(132.89, 1); // centerY can be much lower when shape is a semi circle
});
it('should give space for scale labels', () => {
const gauge = calc({ width: 200, height: 200, shape: 'gauge', showScaleLabels: true, thresholdsBar: true });
expect(gauge.radius).toBeCloseTo(72, 1);
expect(gauge.thresholdsBarRadius).toBeCloseTo(82.66, 1);
expect(gauge.scaleLabelsFontSize).toBeCloseTo(10, 1);
});
});
describe('toRad', () => {
@@ -32,8 +32,13 @@ export interface GaugeDimensions {
barWidth: number;
endAngle?: number;
barIndex: number;
thresholdsBarRadius: number;
thresholdsBarWidth: number;
thresholdsBarSpacing: number;
showScaleLabels?: boolean;
scaleLabelsFontSize: number;
scaleLabelsSpacing: number;
scaleLabelsRadius: number;
}
export function calculateDimensions(
@@ -44,41 +49,72 @@ export function calculateDimensions(
roundedBars: boolean,
barWidthFactor: number,
barIndex: number,
thresholdBar?: boolean
thresholdBar?: boolean,
showScaleLabels?: boolean
): GaugeDimensions {
const yMaxAngle = endAngle > 180 ? 180 : endAngle;
let margin = 0;
if (glow) {
margin = 0.02 * Math.min(width, height);
}
// Max radius based on width
let maxRadiusH = width / 2 - margin;
let maxRadiusW = width / 2 - margin;
// Max radius based on height
let heightRatioV = Math.sin(toRad(yMaxAngle));
let maxRadiusV = (height - margin * 2) / (1 + heightRatioV);
let maxRadiusH = (height - margin * 2) / (1 + heightRatioV);
let maxRadius = Math.min(maxRadiusH, maxRadiusV);
let maxRadius = Math.min(maxRadiusW, maxRadiusH);
let maxRadiusIsLimitedByHeight = maxRadiusH === maxRadius;
let outerRadius = maxRadius;
const barWidth = Math.max(barWidthFactor * (maxRadius / 3), 2);
const thresholdsToBarWidth = 0.2 * Math.pow(barWidth, 0.92);
const thresholdsBarWidth = thresholdBar ? Math.min(Math.max(thresholdsToBarWidth, 4), 12) : 0;
const thresholdsBarSpacing = Math.min(Math.max(thresholdsBarWidth / 2, 2), 12);
let outerRadius = maxRadius;
// If rounded bars is enabled they need a bit more vertical space
if (yMaxAngle < 180 && roundedBars) {
outerRadius -= barWidth;
maxRadiusH -= barWidth;
maxRadiusW -= barWidth;
}
// Scale labels
let scaleLabelsFontSize = 0;
let scaleLabelsSpacing = 0;
let scaleLabelsRadius = 0;
if (showScaleLabels) {
scaleLabelsRadius = outerRadius;
const radiusToFontSizeFactor = 0.12;
scaleLabelsFontSize = Math.max(radiusToFontSizeFactor * Math.pow(outerRadius, 0.92), 10);
scaleLabelsSpacing = scaleLabelsFontSize / 3;
const labelsSize = scaleLabelsFontSize * 1.2 + scaleLabelsSpacing;
outerRadius -= labelsSize;
maxRadiusW -= labelsSize;
maxRadiusH -= labelsSize;
// For gauges the max label needs a bit more vertical space so that it does not get clipped
if (maxRadiusIsLimitedByHeight && endAngle < 180) {
const amount = outerRadius * 0.07;
scaleLabelsRadius -= amount;
maxRadiusH -= amount;
maxRadiusW -= amount;
outerRadius -= amount;
}
}
// Thresholds bar
const thresholdsToBarWidth = 0.2 * Math.pow(barWidth, 0.92);
const thresholdsBarWidth = thresholdBar ? Math.min(Math.max(thresholdsToBarWidth, 4), 12) : 0;
const thresholdsBarSpacing = Math.min(Math.max(thresholdsBarWidth / 2, 2), 12);
let thresholdsBarRadius = 0;
if (thresholdsBarWidth > 0) {
thresholdsBarRadius = outerRadius - thresholdsBarWidth / 2;
maxRadiusW -= thresholdsBarWidth + thresholdsBarSpacing;
maxRadiusH -= thresholdsBarWidth + thresholdsBarSpacing;
maxRadiusV -= thresholdsBarWidth + thresholdsBarSpacing;
outerRadius = Math.min(maxRadiusH, maxRadiusV);
}
if (glow) {
margin = 0.04 * outerRadius;
outerRadius -= (margin * 2) / (1 + heightRatioV);
outerRadius = Math.min(maxRadiusW, maxRadiusH);
}
let innerRadius = outerRadius - barWidth / 2;
@@ -101,5 +137,17 @@ export function calculateDimensions(
barIndex,
thresholdsBarWidth,
thresholdsBarSpacing,
thresholdsBarRadius,
scaleLabelsFontSize,
scaleLabelsSpacing,
scaleLabelsRadius,
};
}
export function toCartesian(centerX: number, centerY: number, radius: number, angleInDegrees: number) {
let radian = ((angleInDegrees - 90) * Math.PI) / 180.0;
return {
x: centerX + radius * Math.cos(radian),
y: centerY + radius * Math.sin(radian),
};
}
@@ -43,6 +43,7 @@ export function RadialBarPanel({
segmentCount={options.segmentCount}
segmentSpacing={options.segmentSpacing}
thresholdsBar={options.showThresholdMarkers}
showScaleLabels={options.showThresholdLabels}
alignmentFactors={valueProps.alignmentFactors}
valueManualFontSize={options.text?.valueSize}
nameManualFontSize={options.text?.titleSize}
@@ -94,6 +94,13 @@ export const plugin = new PanelPlugin<Options>(RadialBarPanel)
defaultValue: defaultOptions.showThresholdMarkers,
});
builder.addBooleanSwitch({
path: 'showThresholdLabels',
name: t('radialbar.config.threshold-labels', 'Show threshold labels'),
category,
defaultValue: defaultOptions.showThresholdLabels,
});
builder.addCustomEditor({
id: 'radialbar-effects',
path: 'effects',
@@ -35,6 +35,7 @@ composableKinds: PanelCfg: {
Options: {
common.SingleStatBaseOptions
showThresholdMarkers: bool | *true
showThresholdLabels: bool | *false
segmentCount: number | *1
segmentSpacing: number | *0.3
sparkline?: bool | *false
@@ -31,6 +31,7 @@ export interface Options extends common.SingleStatBaseOptions {
segmentCount: number;
segmentSpacing: number;
shape: ('circle' | 'gauge');
showThresholdLabels: boolean;
showThresholdMarkers: boolean;
sparkline?: boolean;
}
@@ -42,6 +43,7 @@ export const defaultOptions: Partial<Options> = {
segmentCount: 1,
segmentSpacing: 0.3,
shape: 'gauge',
showThresholdLabels: false,
showThresholdMarkers: true,
sparkline: false,
};
+3 -1
View File
@@ -7699,7 +7699,8 @@
"name-neutral": "Neutral",
"name-show-threshold-labels": "Show threshold labels",
"name-show-threshold-markers": "Show threshold markers",
"placeholder-neutral": "auto"
"placeholder-neutral": "auto",
"threshold": "Threshold {{value}}"
},
"gen-ai": {
"apply-suggestion": "Apply",
@@ -12076,6 +12077,7 @@
"shape-circle": "Circle",
"shape-gauge": "Gauge",
"sparkline": "Show sparkline",
"threshold-labels": "Show threshold labels",
"threshold-markers": "Show thresholds"
}
},