mirror of
https://github.com/grafana/grafana.git
synced 2025-08-01 23:43:26 +08:00
StatPanel: ColorMode, GraphMode & JustifyMode changes (#20680)
* StatPanel: Options rethink * Changed options to string based * -Fixed tests * Refactoring moving files * Refactoring alignment factors * Added alignment factors * Added basic test * Added unit test for layout * Font size handling * Font sizing works * Progress on sizing * Updated * Minor update * Updated * Updated * Removed line option * updated * Updated * Updated * Updated * Highlight last point * Fixed tests * Code refactoring and cleanup * updated * Updated snapshot
This commit is contained in:
@ -20,190 +20,15 @@
|
|||||||
{
|
{
|
||||||
"datasource": null,
|
"datasource": null,
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
"h": 7,
|
"h": 2,
|
||||||
"w": 20,
|
"w": 24,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 0
|
"y": 0
|
||||||
},
|
},
|
||||||
"id": 2,
|
|
||||||
"interval": "10m",
|
|
||||||
"options": {
|
|
||||||
"colorMode": 0,
|
|
||||||
"displayMode": 2,
|
|
||||||
"fieldOptions": {
|
|
||||||
"calcs": ["mean"],
|
|
||||||
"defaults": {
|
|
||||||
"mappings": [],
|
|
||||||
"max": 100,
|
|
||||||
"min": 0,
|
|
||||||
"thresholds": [
|
|
||||||
{
|
|
||||||
"color": "blue",
|
|
||||||
"value": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"color": "green",
|
|
||||||
"value": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"color": "purple",
|
|
||||||
"value": 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"color": "orange",
|
|
||||||
"value": 40
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"color": "red",
|
|
||||||
"value": 80
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"unit": "percent"
|
|
||||||
},
|
|
||||||
"override": {},
|
|
||||||
"values": false
|
|
||||||
},
|
|
||||||
"orientation": "auto",
|
|
||||||
"sparkline": {
|
|
||||||
"show": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"pluginVersion": "6.5.0-pre",
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"refId": "A",
|
|
||||||
"scenarioId": "random_walk"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"refId": "B",
|
|
||||||
"scenarioId": "random_walk"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"refId": "C",
|
|
||||||
"scenarioId": "random_walk"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"refId": "D",
|
|
||||||
"scenarioId": "random_walk"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"refId": "E",
|
|
||||||
"scenarioId": "random_walk"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"refId": "F",
|
|
||||||
"scenarioId": "random_walk"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"refId": "G",
|
|
||||||
"scenarioId": "random_walk"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timeFrom": null,
|
|
||||||
"timeShift": null,
|
|
||||||
"title": "Panel Title",
|
|
||||||
"type": "stat"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": null,
|
|
||||||
"gridPos": {
|
|
||||||
"h": 20,
|
|
||||||
"w": 4,
|
|
||||||
"x": 20,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"id": 8,
|
|
||||||
"interval": "10m",
|
|
||||||
"options": {
|
|
||||||
"colorMode": 0,
|
|
||||||
"displayMode": 2,
|
|
||||||
"fieldOptions": {
|
|
||||||
"calcs": ["mean"],
|
|
||||||
"defaults": {
|
|
||||||
"mappings": [],
|
|
||||||
"max": 100,
|
|
||||||
"min": 0,
|
|
||||||
"thresholds": [
|
|
||||||
{
|
|
||||||
"color": "blue",
|
|
||||||
"value": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"color": "green",
|
|
||||||
"value": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"color": "purple",
|
|
||||||
"value": 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"color": "orange",
|
|
||||||
"value": 40
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"color": "red",
|
|
||||||
"value": 80
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"unit": "percent"
|
|
||||||
},
|
|
||||||
"override": {},
|
|
||||||
"values": false
|
|
||||||
},
|
|
||||||
"orientation": "auto",
|
|
||||||
"sparkline": {
|
|
||||||
"show": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"pluginVersion": "6.5.0-pre",
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"refId": "A",
|
|
||||||
"scenarioId": "random_walk"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"refId": "B",
|
|
||||||
"scenarioId": "random_walk"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"refId": "C",
|
|
||||||
"scenarioId": "random_walk"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"refId": "D",
|
|
||||||
"scenarioId": "random_walk"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"refId": "E",
|
|
||||||
"scenarioId": "random_walk"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"refId": "F",
|
|
||||||
"scenarioId": "random_walk"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"refId": "G",
|
|
||||||
"scenarioId": "random_walk"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timeFrom": null,
|
|
||||||
"timeShift": null,
|
|
||||||
"title": "Panel Title",
|
|
||||||
"type": "stat"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": null,
|
|
||||||
"gridPos": {
|
|
||||||
"h": 3,
|
|
||||||
"w": 20,
|
|
||||||
"x": 0,
|
|
||||||
"y": 7
|
|
||||||
},
|
|
||||||
"id": 6,
|
"id": 6,
|
||||||
"interval": "10m",
|
"interval": "10m",
|
||||||
"options": {
|
"options": {
|
||||||
"colorMode": 0,
|
"colorMode": "background",
|
||||||
"displayMode": 2,
|
|
||||||
"fieldOptions": {
|
"fieldOptions": {
|
||||||
"calcs": ["mean"],
|
"calcs": ["mean"],
|
||||||
"defaults": {
|
"defaults": {
|
||||||
@ -237,60 +62,64 @@
|
|||||||
"override": {},
|
"override": {},
|
||||||
"values": false
|
"values": false
|
||||||
},
|
},
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
"orientation": "auto",
|
"orientation": "auto",
|
||||||
"sparkline": {
|
"sparkline": {
|
||||||
"show": true
|
"show": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pluginVersion": "6.5.0-pre",
|
"pluginVersion": "6.6.0-pre",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
|
"alias": "A longer title",
|
||||||
"refId": "A",
|
"refId": "A",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"refId": "B",
|
"alias": "AB",
|
||||||
"scenarioId": "random_walk"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"refId": "C",
|
"refId": "C",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "CPU",
|
||||||
"refId": "D",
|
"refId": "D",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "Google",
|
||||||
|
"labels": "",
|
||||||
"refId": "E",
|
"refId": "E",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "Even longer road title",
|
||||||
"refId": "F",
|
"refId": "F",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "Why does it have to",
|
||||||
"refId": "G",
|
"refId": "G",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"timeFrom": null,
|
"timeFrom": null,
|
||||||
"timeShift": null,
|
"timeShift": null,
|
||||||
"title": "Panel Title",
|
"title": "",
|
||||||
"type": "stat"
|
"type": "stat"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"datasource": null,
|
"datasource": null,
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
"h": 6,
|
"h": 5,
|
||||||
"w": 20,
|
"w": 24,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 10
|
"y": 2
|
||||||
},
|
},
|
||||||
"id": 3,
|
"id": 10,
|
||||||
"interval": "10m",
|
"interval": "10m",
|
||||||
"options": {
|
"options": {
|
||||||
"colorMode": 0,
|
"colorMode": "background",
|
||||||
"displayMode": 3,
|
|
||||||
"fieldOptions": {
|
"fieldOptions": {
|
||||||
"calcs": ["mean"],
|
"calcs": ["mean"],
|
||||||
"defaults": {
|
"defaults": {
|
||||||
@ -319,43 +148,48 @@
|
|||||||
"value": 80
|
"value": 80
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"unit": "percent"
|
"unit": "areaM2"
|
||||||
},
|
},
|
||||||
"override": {},
|
"override": {},
|
||||||
"values": false
|
"values": false
|
||||||
},
|
},
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
"orientation": "auto",
|
"orientation": "auto",
|
||||||
"sparkline": {
|
"sparkline": {
|
||||||
"show": true
|
"show": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pluginVersion": "6.5.0-pre",
|
"pluginVersion": "6.6.0-pre",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
|
"alias": "AB",
|
||||||
"refId": "A",
|
"refId": "A",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"refId": "B",
|
"alias": "CPU",
|
||||||
"scenarioId": "random_walk"
|
"labels": "",
|
||||||
},
|
|
||||||
{
|
|
||||||
"refId": "C",
|
"refId": "C",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "Longer title",
|
||||||
"refId": "D",
|
"refId": "D",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "Even longer title",
|
||||||
"refId": "E",
|
"refId": "E",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "Outside",
|
||||||
"refId": "F",
|
"refId": "F",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "Inside",
|
||||||
"refId": "G",
|
"refId": "G",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
}
|
}
|
||||||
@ -368,16 +202,15 @@
|
|||||||
{
|
{
|
||||||
"datasource": null,
|
"datasource": null,
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
"h": 6,
|
"h": 5,
|
||||||
"w": 20,
|
"w": 24,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 16
|
"y": 7
|
||||||
},
|
},
|
||||||
"id": 4,
|
"id": 11,
|
||||||
"interval": "10m",
|
"interval": "10m",
|
||||||
"options": {
|
"options": {
|
||||||
"colorMode": 0,
|
"colorMode": "value",
|
||||||
"displayMode": 0,
|
|
||||||
"fieldOptions": {
|
"fieldOptions": {
|
||||||
"calcs": ["mean"],
|
"calcs": ["mean"],
|
||||||
"defaults": {
|
"defaults": {
|
||||||
@ -411,12 +244,99 @@
|
|||||||
"override": {},
|
"override": {},
|
||||||
"values": false
|
"values": false
|
||||||
},
|
},
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
"orientation": "auto",
|
"orientation": "auto",
|
||||||
"sparkline": {
|
"sparkline": {
|
||||||
"show": true
|
"show": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pluginVersion": "6.5.0-pre",
|
"pluginVersion": "6.6.0-pre",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"refId": "A",
|
||||||
|
"scenarioId": "random_walk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"refId": "C",
|
||||||
|
"scenarioId": "random_walk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"refId": "D",
|
||||||
|
"scenarioId": "random_walk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"refId": "E",
|
||||||
|
"scenarioId": "random_walk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"refId": "F",
|
||||||
|
"scenarioId": "random_walk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"refId": "G",
|
||||||
|
"scenarioId": "random_walk"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "Panel Title",
|
||||||
|
"transparent": true,
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": null,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 14,
|
||||||
|
"w": 6,
|
||||||
|
"x": 0,
|
||||||
|
"y": 12
|
||||||
|
},
|
||||||
|
"id": 13,
|
||||||
|
"interval": "10m",
|
||||||
|
"options": {
|
||||||
|
"colorMode": "background",
|
||||||
|
"fieldOptions": {
|
||||||
|
"calcs": ["mean"],
|
||||||
|
"defaults": {
|
||||||
|
"mappings": [],
|
||||||
|
"max": 100,
|
||||||
|
"min": 0,
|
||||||
|
"thresholds": [
|
||||||
|
{
|
||||||
|
"color": "blue",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "purple",
|
||||||
|
"value": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "orange",
|
||||||
|
"value": 40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"unit": "percent"
|
||||||
|
},
|
||||||
|
"override": {},
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"sparkline": {
|
||||||
|
"show": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "6.6.0-pre",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"refId": "A",
|
"refId": "A",
|
||||||
@ -455,16 +375,15 @@
|
|||||||
{
|
{
|
||||||
"datasource": null,
|
"datasource": null,
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
"h": 21,
|
"h": 11,
|
||||||
"w": 4,
|
"w": 4,
|
||||||
"x": 20,
|
"x": 6,
|
||||||
"y": 20
|
"y": 12
|
||||||
},
|
},
|
||||||
"id": 9,
|
"id": 8,
|
||||||
"interval": "10m",
|
"interval": "10m",
|
||||||
"options": {
|
"options": {
|
||||||
"colorMode": 0,
|
"colorMode": "background",
|
||||||
"displayMode": 0,
|
|
||||||
"fieldOptions": {
|
"fieldOptions": {
|
||||||
"calcs": ["mean"],
|
"calcs": ["mean"],
|
||||||
"defaults": {
|
"defaults": {
|
||||||
@ -498,12 +417,14 @@
|
|||||||
"override": {},
|
"override": {},
|
||||||
"values": false
|
"values": false
|
||||||
},
|
},
|
||||||
|
"graphMode": "line",
|
||||||
|
"justifyMode": "auto",
|
||||||
"orientation": "auto",
|
"orientation": "auto",
|
||||||
"sparkline": {
|
"sparkline": {
|
||||||
"show": true
|
"show": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pluginVersion": "6.5.0-pre",
|
"pluginVersion": "6.6.0-pre",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"refId": "A",
|
"refId": "A",
|
||||||
@ -542,16 +463,15 @@
|
|||||||
{
|
{
|
||||||
"datasource": null,
|
"datasource": null,
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
"h": 6,
|
"h": 9,
|
||||||
"w": 20,
|
"w": 8,
|
||||||
"x": 0,
|
"x": 10,
|
||||||
"y": 22
|
"y": 12
|
||||||
},
|
},
|
||||||
"id": 5,
|
"id": 12,
|
||||||
"interval": "10m",
|
"interval": "10m",
|
||||||
"options": {
|
"options": {
|
||||||
"colorMode": 0,
|
"colorMode": "background",
|
||||||
"displayMode": 1,
|
|
||||||
"fieldOptions": {
|
"fieldOptions": {
|
||||||
"calcs": ["mean"],
|
"calcs": ["mean"],
|
||||||
"defaults": {
|
"defaults": {
|
||||||
@ -585,12 +505,14 @@
|
|||||||
"override": {},
|
"override": {},
|
||||||
"values": false
|
"values": false
|
||||||
},
|
},
|
||||||
"orientation": "auto",
|
"graphMode": "line",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "horizontal",
|
||||||
"sparkline": {
|
"sparkline": {
|
||||||
"show": true
|
"show": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pluginVersion": "6.5.0-pre",
|
"pluginVersion": "6.6.0-pre",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"refId": "A",
|
"refId": "A",
|
||||||
@ -627,7 +549,7 @@
|
|||||||
"type": "stat"
|
"type": "stat"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"schemaVersion": 20,
|
"schemaVersion": 21,
|
||||||
"style": "dark",
|
"style": "dark",
|
||||||
"tags": ["gdev", "panel-tests"],
|
"tags": ["gdev", "panel-tests"],
|
||||||
"templating": {
|
"templating": {
|
||||||
@ -643,5 +565,5 @@
|
|||||||
"timezone": "",
|
"timezone": "",
|
||||||
"title": "Panel Tests - Stat",
|
"title": "Panel Tests - Stat",
|
||||||
"uid": "jWWHNJpWz",
|
"uid": "jWWHNJpWz",
|
||||||
"version": 6
|
"version": 25
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import { FieldConfig, DataFrame, FieldType } from '../types/dataFrame';
|
|||||||
import { InterpolateFunction } from '../types/panel';
|
import { InterpolateFunction } from '../types/panel';
|
||||||
import { DataFrameView } from '../dataframe/DataFrameView';
|
import { DataFrameView } from '../dataframe/DataFrameView';
|
||||||
import { GraphSeriesValue } from '../types/graph';
|
import { GraphSeriesValue } from '../types/graph';
|
||||||
import { DisplayValue } from '../types/displayValue';
|
import { DisplayValue, DisplayValueAlignmentFactors } from '../types/displayValue';
|
||||||
import { GrafanaTheme } from '../types/theme';
|
import { GrafanaTheme } from '../types/theme';
|
||||||
import { ReducerID, reduceField } from '../transformations/fieldReducer';
|
import { ReducerID, reduceField } from '../transformations/fieldReducer';
|
||||||
import { ScopedVars } from '../types/ScopedVars';
|
import { ScopedVars } from '../types/ScopedVars';
|
||||||
@ -21,6 +21,7 @@ export interface FieldDisplayOptions {
|
|||||||
defaults: FieldConfig; // Use these values unless otherwise stated
|
defaults: FieldConfig; // Use these values unless otherwise stated
|
||||||
override: FieldConfig; // Set these values regardless of the source
|
override: FieldConfig; // Set these values regardless of the source
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: use built in variables, same as for data links?
|
// TODO: use built in variables, same as for data links?
|
||||||
export const VAR_SERIES_NAME = '__series.name';
|
export const VAR_SERIES_NAME = '__series.name';
|
||||||
export const VAR_FIELD_NAME = '__field.name';
|
export const VAR_FIELD_NAME = '__field.name';
|
||||||
@ -278,3 +279,22 @@ export function getFieldProperties(...props: FieldConfig[]): FieldConfig {
|
|||||||
}
|
}
|
||||||
return field;
|
return field;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getDisplayValueAlignmentFactors(values: FieldDisplay[]): DisplayValueAlignmentFactors {
|
||||||
|
const info: DisplayValueAlignmentFactors = {
|
||||||
|
title: '',
|
||||||
|
text: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let i = 0; i < values.length; i++) {
|
||||||
|
const v = values[i].display;
|
||||||
|
if (v.text && v.text.length > info.text.length) {
|
||||||
|
info.text = v.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v.title && v.title.length > info.title.length) {
|
||||||
|
info.title = v.title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
@ -8,6 +8,15 @@ export interface DisplayValue {
|
|||||||
fontSize?: string;
|
fontSize?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These represents the displau value with the longest title and text.
|
||||||
|
* Used to align widths and heights when displaying multiple DisplayValues
|
||||||
|
*/
|
||||||
|
export interface DisplayValueAlignmentFactors {
|
||||||
|
title: string;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type DecimalCount = number | null | undefined;
|
export type DecimalCount = number | null | undefined;
|
||||||
|
|
||||||
export interface DecimalInfo {
|
export interface DecimalInfo {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { DisplayValue } from './displayValue';
|
import { DisplayValue } from './displayValue';
|
||||||
import { Field } from './dataFrame';
|
import { Field } from './dataFrame';
|
||||||
|
|
||||||
export interface YAxis {
|
export interface YAxis {
|
||||||
index: number;
|
index: number;
|
||||||
min?: number;
|
min?: number;
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
// Library
|
// Library
|
||||||
import React, { PureComponent, CSSProperties, ReactNode } from 'react';
|
import React, { PureComponent, CSSProperties, ReactNode } from 'react';
|
||||||
import tinycolor from 'tinycolor2';
|
import tinycolor from 'tinycolor2';
|
||||||
import { Threshold, TimeSeriesValue, getActiveThreshold, DisplayValue } from '@grafana/data';
|
import {
|
||||||
|
Threshold,
|
||||||
|
TimeSeriesValue,
|
||||||
|
getActiveThreshold,
|
||||||
|
DisplayValue,
|
||||||
|
DisplayValueAlignmentFactors,
|
||||||
|
} from '@grafana/data';
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
import { getColorFromHexRgbOrName } from '@grafana/data';
|
import { getColorFromHexRgbOrName } from '@grafana/data';
|
||||||
import { measureText } from '../../utils/measureText';
|
import { measureText, calculateFontSize } from '../../utils/measureText';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { VizOrientation } from '@grafana/data';
|
import { VizOrientation } from '@grafana/data';
|
||||||
@ -19,18 +25,6 @@ const TITLE_LINE_HEIGHT = 1.5;
|
|||||||
const VALUE_LINE_HEIGHT = 1;
|
const VALUE_LINE_HEIGHT = 1;
|
||||||
const VALUE_LEFT_PADDING = 10;
|
const VALUE_LEFT_PADDING = 10;
|
||||||
|
|
||||||
/**
|
|
||||||
* These values calculate the internal font sizes and
|
|
||||||
* placement. For consistent behavior across repeating
|
|
||||||
* panels, we can optionally pass in the maximum values.
|
|
||||||
*
|
|
||||||
* If performace becomes a problem, we can cache the results
|
|
||||||
*/
|
|
||||||
export interface BarGaugeAlignmentFactors {
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Props extends Themeable {
|
export interface Props extends Themeable {
|
||||||
height: number;
|
height: number;
|
||||||
width: number;
|
width: number;
|
||||||
@ -43,7 +37,7 @@ export interface Props extends Themeable {
|
|||||||
displayMode: 'basic' | 'lcd' | 'gradient';
|
displayMode: 'basic' | 'lcd' | 'gradient';
|
||||||
onClick?: React.MouseEventHandler<HTMLElement>;
|
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||||
className?: string;
|
className?: string;
|
||||||
alignmentFactors?: BarGaugeAlignmentFactors;
|
alignmentFactors?: DisplayValueAlignmentFactors;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BarGauge extends PureComponent<Props> {
|
export class BarGauge extends PureComponent<Props> {
|
||||||
@ -537,14 +531,6 @@ function getValueStyles(
|
|||||||
textWidth -= VALUE_LEFT_PADDING;
|
textWidth -= VALUE_LEFT_PADDING;
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate width in 14px
|
valueStyles.fontSize = calculateFontSize(value, textWidth, height, VALUE_LINE_HEIGHT) + 'px';
|
||||||
const textSize = measureText(value, 14);
|
|
||||||
// how much bigger than 14px can we make it while staying within our width constraints
|
|
||||||
const fontSizeBasedOnWidth = (textWidth / (textSize.width + 2)) * 14;
|
|
||||||
const fontSizeBasedOnHeight = height / VALUE_LINE_HEIGHT;
|
|
||||||
|
|
||||||
// final fontSize
|
|
||||||
valueStyles.fontSize = Math.min(fontSizeBasedOnHeight, fontSizeBasedOnWidth).toFixed(4) + 'px';
|
|
||||||
|
|
||||||
return valueStyles;
|
return valueStyles;
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ exports[`BarGauge Render with basic options should render 1`] = `
|
|||||||
"alignItems": "center",
|
"alignItems": "center",
|
||||||
"color": "#73BF69",
|
"color": "#73BF69",
|
||||||
"display": "flex",
|
"display": "flex",
|
||||||
"fontSize": "175.0000px",
|
"fontSize": "175px",
|
||||||
"height": "300px",
|
"height": "300px",
|
||||||
"justifyContent": "flex-start",
|
"justifyContent": "flex-start",
|
||||||
"lineHeight": 1,
|
"lineHeight": 1,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { storiesOf } from '@storybook/react';
|
import { storiesOf } from '@storybook/react';
|
||||||
import { text } from '@storybook/addon-knobs';
|
import { text } from '@storybook/addon-knobs';
|
||||||
import { BigValue, BigValueDisplayMode } from './BigValue';
|
import { BigValue, BigValueColorMode, BigValueGraphMode } from './BigValue';
|
||||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||||
import { renderComponentWithTheme } from '../../utils/storybook/withTheme';
|
import { renderComponentWithTheme } from '../../utils/storybook/withTheme';
|
||||||
|
|
||||||
@ -16,20 +16,22 @@ const BigValueStories = storiesOf('UI/BigValue', module);
|
|||||||
BigValueStories.addDecorator(withCenteredStory);
|
BigValueStories.addDecorator(withCenteredStory);
|
||||||
|
|
||||||
interface StoryOptions {
|
interface StoryOptions {
|
||||||
mode: BigValueDisplayMode;
|
colorMode: BigValueColorMode;
|
||||||
|
graphMode: BigValueGraphMode;
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
noSparkline?: boolean;
|
noSparkline?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addStoryForMode(options: StoryOptions) {
|
function addStoryForMode(options: StoryOptions) {
|
||||||
BigValueStories.add(`Mode: ${BigValueDisplayMode[options.mode]}`, () => {
|
BigValueStories.add(`Color: ${options.colorMode}`, () => {
|
||||||
const { value, title } = getKnobs();
|
const { value, title } = getKnobs();
|
||||||
|
|
||||||
return renderComponentWithTheme(BigValue, {
|
return renderComponentWithTheme(BigValue, {
|
||||||
width: options.width || 400,
|
width: options.width || 400,
|
||||||
height: options.height || 300,
|
height: options.height || 300,
|
||||||
displayMode: options.mode,
|
colorMode: options.colorMode,
|
||||||
|
graphMode: options.graphMode,
|
||||||
value: {
|
value: {
|
||||||
text: value,
|
text: value,
|
||||||
numeric: 5022,
|
numeric: 5022,
|
||||||
@ -52,7 +54,5 @@ function addStoryForMode(options: StoryOptions) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
addStoryForMode({ mode: BigValueDisplayMode.Classic });
|
addStoryForMode({ colorMode: BigValueColorMode.Value, graphMode: BigValueGraphMode.Area });
|
||||||
addStoryForMode({ mode: BigValueDisplayMode.Classic2 });
|
addStoryForMode({ colorMode: BigValueColorMode.Background, graphMode: BigValueGraphMode.Line });
|
||||||
addStoryForMode({ mode: BigValueDisplayMode.Vibrant });
|
|
||||||
addStoryForMode({ mode: BigValueDisplayMode.Vibrant2 });
|
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import { BigValue, Props, BigValueDisplayMode } from './BigValue';
|
import { BigValue, Props, BigValueColorMode, BigValueGraphMode } from './BigValue';
|
||||||
import { getTheme } from '../../themes/index';
|
import { getTheme } from '../../themes';
|
||||||
|
|
||||||
jest.mock('jquery', () => ({
|
function getProps(propOverrides?: Partial<Props>): Props {
|
||||||
plot: jest.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const setup = (propOverrides?: object) => {
|
|
||||||
const props: Props = {
|
const props: Props = {
|
||||||
|
colorMode: BigValueColorMode.Background,
|
||||||
|
graphMode: BigValueGraphMode.Line,
|
||||||
height: 300,
|
height: 300,
|
||||||
width: 300,
|
width: 300,
|
||||||
displayMode: BigValueDisplayMode.Classic,
|
|
||||||
value: {
|
value: {
|
||||||
text: '25',
|
text: '25',
|
||||||
numeric: 25,
|
numeric: 25,
|
||||||
@ -20,7 +17,11 @@ const setup = (propOverrides?: object) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Object.assign(props, propOverrides);
|
Object.assign(props, propOverrides);
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
|
||||||
|
const setup = (propOverrides?: object) => {
|
||||||
|
const props = getProps(propOverrides);
|
||||||
const wrapper = shallow(<BigValue {...props} />);
|
const wrapper = shallow(<BigValue {...props} />);
|
||||||
const instance = wrapper.instance() as BigValue;
|
const instance = wrapper.instance() as BigValue;
|
||||||
|
|
||||||
@ -30,10 +31,11 @@ const setup = (propOverrides?: object) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('Render SingleStat with basic options', () => {
|
describe('BigValue', () => {
|
||||||
|
describe('Render with basic options', () => {
|
||||||
it('should render', () => {
|
it('should render', () => {
|
||||||
const { wrapper } = setup();
|
const { wrapper } = setup();
|
||||||
expect(wrapper).toBeDefined();
|
expect(wrapper).toMatchSnapshot();
|
||||||
// expect(wrapper).toMatchSnapshot();
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,26 +1,39 @@
|
|||||||
// Library
|
// Library
|
||||||
import React, { PureComponent, CSSProperties } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import tinycolor from 'tinycolor2';
|
import { DisplayValue, GraphSeriesValue, DisplayValueAlignmentFactors } from '@grafana/data';
|
||||||
import { Chart, Geom } from 'bizcharts';
|
|
||||||
import { DisplayValue } from '@grafana/data';
|
|
||||||
|
|
||||||
// Utils
|
|
||||||
import { getColorFromHexRgbOrName, GrafanaTheme } from '@grafana/data';
|
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { Themeable } from '../../types';
|
import { Themeable } from '../../types';
|
||||||
|
import {
|
||||||
|
calculateLayout,
|
||||||
|
getPanelStyles,
|
||||||
|
getValueAndTitleContainerStyles,
|
||||||
|
getValueStyles,
|
||||||
|
getTitleStyles,
|
||||||
|
} from './styles';
|
||||||
|
import { renderGraph } from './renderGraph';
|
||||||
|
|
||||||
export interface BigValueSparkline {
|
export interface BigValueSparkline {
|
||||||
data: any[][];
|
data: GraphSeriesValue[][];
|
||||||
minX: number;
|
minX: number;
|
||||||
maxX: number;
|
maxX: number;
|
||||||
|
highlightIndex?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum BigValueDisplayMode {
|
export enum BigValueColorMode {
|
||||||
Classic,
|
Value = 'value',
|
||||||
Classic2,
|
Background = 'background',
|
||||||
Vibrant,
|
}
|
||||||
Vibrant2,
|
|
||||||
|
export enum BigValueGraphMode {
|
||||||
|
None = 'none',
|
||||||
|
Line = 'line',
|
||||||
|
Area = 'area',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum BigValueJustifyMode {
|
||||||
|
Auto = 'auto',
|
||||||
|
Center = 'center',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Props extends Themeable {
|
export interface Props extends Themeable {
|
||||||
@ -30,10 +43,17 @@ export interface Props extends Themeable {
|
|||||||
sparkline?: BigValueSparkline;
|
sparkline?: BigValueSparkline;
|
||||||
onClick?: React.MouseEventHandler<HTMLElement>;
|
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||||
className?: string;
|
className?: string;
|
||||||
displayMode: BigValueDisplayMode;
|
colorMode: BigValueColorMode;
|
||||||
|
graphMode: BigValueGraphMode;
|
||||||
|
justifyMode?: BigValueJustifyMode;
|
||||||
|
alignmentFactors?: DisplayValueAlignmentFactors;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BigValue extends PureComponent<Props> {
|
export class BigValue extends PureComponent<Props> {
|
||||||
|
static defaultProps: Partial<Props> = {
|
||||||
|
justifyMode: BigValueJustifyMode.Auto,
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { value, onClick, className, sparkline } = this.props;
|
const { value, onClick, className, sparkline } = this.props;
|
||||||
|
|
||||||
@ -47,7 +67,7 @@ export class BigValue extends PureComponent<Props> {
|
|||||||
<div className={className} style={panelStyles} onClick={onClick}>
|
<div className={className} style={panelStyles} onClick={onClick}>
|
||||||
<div style={valueAndTitleContainerStyles}>
|
<div style={valueAndTitleContainerStyles}>
|
||||||
{value.title && <div style={titleStyles}>{value.title}</div>}
|
{value.title && <div style={titleStyles}>{value.title}</div>}
|
||||||
<div style={valueStyles}>{value.text}</div>
|
<div style={valueStyles}>{renderValueWithSmallerUnit(value.text, layout.valueFontSize)}</div>
|
||||||
</div>
|
</div>
|
||||||
{renderGraph(layout, sparkline)}
|
{renderGraph(layout, sparkline)}
|
||||||
</div>
|
</div>
|
||||||
@ -55,360 +75,18 @@ export class BigValue extends PureComponent<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const MIN_VALUE_FONT_SIZE = 20;
|
function renderValueWithSmallerUnit(value: string, fontSize: number) {
|
||||||
const MAX_VALUE_FONT_SIZE = 50;
|
const valueParts = value.split(' ');
|
||||||
const MIN_TITLE_FONT_SIZE = 14;
|
const unitSize = `${fontSize * 0.7}px`;
|
||||||
const TITLE_VALUE_RATIO = 0.45;
|
|
||||||
const VALUE_HEIGHT_RATIO = 0.25;
|
|
||||||
const VALUE_HEIGHT_RATIO_WIDE = 0.3;
|
|
||||||
const LINE_HEIGHT = 1.2;
|
|
||||||
const PANEL_PADDING = 16;
|
|
||||||
const CHART_TOP_MARGIN = 8;
|
|
||||||
|
|
||||||
interface LayoutResult {
|
|
||||||
titleFontSize: number;
|
|
||||||
valueFontSize: number;
|
|
||||||
chartHeight: number;
|
|
||||||
chartWidth: number;
|
|
||||||
type: LayoutType;
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
displayMode: BigValueDisplayMode;
|
|
||||||
theme: GrafanaTheme;
|
|
||||||
valueColor: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum LayoutType {
|
|
||||||
Stacked,
|
|
||||||
StackedNoChart,
|
|
||||||
Wide,
|
|
||||||
WideNoChart,
|
|
||||||
}
|
|
||||||
|
|
||||||
export function calculateLayout(props: Props): LayoutResult {
|
|
||||||
const { width, height, sparkline, displayMode, theme, value } = props;
|
|
||||||
const useWideLayout = width / height > 2.8;
|
|
||||||
const valueColor = getColorFromHexRgbOrName(value.color || 'green', theme.type);
|
|
||||||
|
|
||||||
// handle wide layouts
|
|
||||||
if (useWideLayout) {
|
|
||||||
const valueFontSize = Math.min(
|
|
||||||
Math.max(height * VALUE_HEIGHT_RATIO_WIDE, MIN_VALUE_FONT_SIZE),
|
|
||||||
MAX_VALUE_FONT_SIZE
|
|
||||||
);
|
|
||||||
const titleFontSize = Math.max(valueFontSize * TITLE_VALUE_RATIO, MIN_TITLE_FONT_SIZE);
|
|
||||||
|
|
||||||
const chartHeight = height - PANEL_PADDING * 2;
|
|
||||||
const chartWidth = width / 2;
|
|
||||||
let type = !!sparkline ? LayoutType.Wide : LayoutType.WideNoChart;
|
|
||||||
|
|
||||||
if (height < 80 || !sparkline) {
|
|
||||||
type = LayoutType.WideNoChart;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
valueFontSize,
|
|
||||||
titleFontSize,
|
|
||||||
chartHeight,
|
|
||||||
chartWidth,
|
|
||||||
type,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
displayMode,
|
|
||||||
theme,
|
|
||||||
valueColor,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle stacked layouts
|
|
||||||
const valueFontSize = Math.min(Math.max(height * VALUE_HEIGHT_RATIO, MIN_VALUE_FONT_SIZE), MAX_VALUE_FONT_SIZE);
|
|
||||||
const titleFontSize = Math.max(valueFontSize * TITLE_VALUE_RATIO, MIN_TITLE_FONT_SIZE);
|
|
||||||
const valueHeight = valueFontSize * LINE_HEIGHT;
|
|
||||||
const titleHeight = titleFontSize * LINE_HEIGHT;
|
|
||||||
|
|
||||||
let chartHeight = height - valueHeight - titleHeight - PANEL_PADDING * 2 - CHART_TOP_MARGIN;
|
|
||||||
let chartWidth = width - PANEL_PADDING * 2;
|
|
||||||
let type = LayoutType.Stacked;
|
|
||||||
|
|
||||||
if (height < 100 || !sparkline) {
|
|
||||||
type = LayoutType.StackedNoChart;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (displayMode) {
|
|
||||||
case BigValueDisplayMode.Vibrant2:
|
|
||||||
case BigValueDisplayMode.Classic:
|
|
||||||
case BigValueDisplayMode.Classic2:
|
|
||||||
chartWidth = width;
|
|
||||||
chartHeight += PANEL_PADDING;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
valueFontSize,
|
|
||||||
titleFontSize,
|
|
||||||
chartHeight,
|
|
||||||
chartWidth,
|
|
||||||
type,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
displayMode,
|
|
||||||
theme,
|
|
||||||
valueColor,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTitleStyles(layout: LayoutResult) {
|
|
||||||
const styles: CSSProperties = {
|
|
||||||
fontSize: `${layout.titleFontSize}px`,
|
|
||||||
textShadow: '#333 0px 0px 1px',
|
|
||||||
color: '#EEE',
|
|
||||||
};
|
|
||||||
|
|
||||||
if (layout.theme.isLight) {
|
|
||||||
styles.color = 'white';
|
|
||||||
}
|
|
||||||
|
|
||||||
return styles;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getValueStyles(layout: LayoutResult) {
|
|
||||||
const styles: CSSProperties = {
|
|
||||||
fontSize: `${layout.valueFontSize}px`,
|
|
||||||
color: '#EEE',
|
|
||||||
textShadow: '#333 0px 0px 1px',
|
|
||||||
fontWeight: 500,
|
|
||||||
lineHeight: LINE_HEIGHT,
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (layout.displayMode) {
|
|
||||||
case BigValueDisplayMode.Classic:
|
|
||||||
case BigValueDisplayMode.Classic2:
|
|
||||||
styles.color = layout.valueColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
return styles;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getValueAndTitleContainerStyles(layout: LayoutResult): CSSProperties {
|
|
||||||
switch (layout.type) {
|
|
||||||
case LayoutType.Wide:
|
|
||||||
return {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
flexGrow: 1,
|
|
||||||
};
|
|
||||||
case LayoutType.WideNoChart:
|
|
||||||
return {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
flexGrow: 1,
|
|
||||||
};
|
|
||||||
case LayoutType.StackedNoChart:
|
|
||||||
return {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
flexGrow: 1,
|
|
||||||
};
|
|
||||||
case LayoutType.Stacked:
|
|
||||||
default:
|
|
||||||
return {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
justifyContent: 'center',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getPanelStyles(layout: LayoutResult) {
|
|
||||||
const panelStyles: CSSProperties = {
|
|
||||||
width: `${layout.width}px`,
|
|
||||||
height: `${layout.height}px`,
|
|
||||||
padding: `${PANEL_PADDING}px`,
|
|
||||||
borderRadius: '3px',
|
|
||||||
position: 'relative',
|
|
||||||
display: 'flex',
|
|
||||||
};
|
|
||||||
|
|
||||||
const themeFactor = layout.theme.isDark ? 1 : -0.7;
|
|
||||||
|
|
||||||
switch (layout.displayMode) {
|
|
||||||
case BigValueDisplayMode.Vibrant:
|
|
||||||
case BigValueDisplayMode.Vibrant2:
|
|
||||||
const bgColor2 = tinycolor(layout.valueColor)
|
|
||||||
.darken(15 * themeFactor)
|
|
||||||
.spin(8)
|
|
||||||
.toRgbString();
|
|
||||||
const bgColor3 = tinycolor(layout.valueColor)
|
|
||||||
.darken(5 * themeFactor)
|
|
||||||
.spin(-8)
|
|
||||||
.toRgbString();
|
|
||||||
panelStyles.background = `linear-gradient(120deg, ${bgColor2}, ${bgColor3})`;
|
|
||||||
break;
|
|
||||||
case BigValueDisplayMode.Classic:
|
|
||||||
case BigValueDisplayMode.Classic2:
|
|
||||||
panelStyles.background = `${layout.theme.colors.dark4}`;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (layout.type) {
|
|
||||||
case LayoutType.Stacked:
|
|
||||||
panelStyles.flexDirection = 'column';
|
|
||||||
break;
|
|
||||||
case LayoutType.StackedNoChart:
|
|
||||||
panelStyles.alignItems = 'center';
|
|
||||||
break;
|
|
||||||
case LayoutType.Wide:
|
|
||||||
panelStyles.flexDirection = 'row';
|
|
||||||
panelStyles.alignItems = 'center';
|
|
||||||
panelStyles.justifyContent = 'space-between';
|
|
||||||
break;
|
|
||||||
case LayoutType.WideNoChart:
|
|
||||||
panelStyles.alignItems = 'center';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return panelStyles;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderGraph(layout: LayoutResult, sparkline?: BigValueSparkline) {
|
|
||||||
if (!sparkline) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = sparkline.data.map(values => {
|
|
||||||
return { time: values[0], value: values[1], name: 'A' };
|
|
||||||
});
|
|
||||||
|
|
||||||
const scales = {
|
|
||||||
time: {
|
|
||||||
type: 'time',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const chartStyles: CSSProperties = {
|
|
||||||
marginTop: `${CHART_TOP_MARGIN}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
// default to line graph
|
|
||||||
let geomRender = renderLineGeom;
|
|
||||||
|
|
||||||
switch (layout.type) {
|
|
||||||
case LayoutType.Wide:
|
|
||||||
chartStyles.width = `${layout.chartWidth}px`;
|
|
||||||
chartStyles.height = `${layout.chartHeight}px`;
|
|
||||||
break;
|
|
||||||
case LayoutType.Stacked:
|
|
||||||
chartStyles.position = 'relative';
|
|
||||||
chartStyles.top = '8px';
|
|
||||||
break;
|
|
||||||
case LayoutType.WideNoChart:
|
|
||||||
case LayoutType.StackedNoChart:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (layout.chartWidth === layout.width) {
|
|
||||||
chartStyles.position = 'absolute';
|
|
||||||
chartStyles.bottom = 0;
|
|
||||||
chartStyles.right = 0;
|
|
||||||
chartStyles.left = 0;
|
|
||||||
chartStyles.right = 0;
|
|
||||||
chartStyles.top = 'unset';
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (layout.displayMode) {
|
|
||||||
case BigValueDisplayMode.Vibrant2:
|
|
||||||
geomRender = renderVibrant2Geom;
|
|
||||||
break;
|
|
||||||
case BigValueDisplayMode.Classic:
|
|
||||||
geomRender = renderClassicAreaGeom;
|
|
||||||
break;
|
|
||||||
case BigValueDisplayMode.Classic2:
|
|
||||||
geomRender = renderAreaGeom;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Chart
|
|
||||||
height={layout.chartHeight}
|
|
||||||
width={layout.chartWidth}
|
|
||||||
data={data}
|
|
||||||
animate={false}
|
|
||||||
padding={[4, 0, 0, 0]}
|
|
||||||
scale={scales}
|
|
||||||
style={chartStyles}
|
|
||||||
>
|
|
||||||
{geomRender(layout)}
|
|
||||||
</Chart>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderLineGeom(layout: LayoutResult) {
|
|
||||||
const lineStyle: any = {
|
|
||||||
stroke: '#CCC',
|
|
||||||
lineWidth: 2,
|
|
||||||
shadowBlur: 10,
|
|
||||||
shadowColor: '#444',
|
|
||||||
shadowOffsetY: 7,
|
|
||||||
};
|
|
||||||
|
|
||||||
return <Geom type="line" position="time*value" size={2} color="white" style={lineStyle} shape="smooth" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderVibrant2Geom(layout: LayoutResult) {
|
|
||||||
const lineStyle: any = {
|
|
||||||
stroke: '#CCC',
|
|
||||||
lineWidth: 2,
|
|
||||||
shadowBlur: 10,
|
|
||||||
shadowColor: '#444',
|
|
||||||
shadowOffsetY: -5,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
if (valueParts.length === 2) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Geom type="area" position="time*value" size={0} color="rgba(255,255,255,0.4)" style={lineStyle} shape="smooth" />
|
{valueParts[0]}
|
||||||
<Geom type="line" position="time*value" size={1} color="white" style={lineStyle} shape="smooth" />
|
<span style={{ fontSize: unitSize, paddingLeft: '2px' }}>{valueParts[1]}</span>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderClassicAreaGeom(layout: LayoutResult) {
|
return value;
|
||||||
const lineStyle: any = {
|
|
||||||
opacity: 1,
|
|
||||||
fillOpacity: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
const fillColor = tinycolor(layout.valueColor)
|
|
||||||
.setAlpha(0.2)
|
|
||||||
.toRgbString();
|
|
||||||
|
|
||||||
lineStyle.stroke = layout.valueColor;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Geom type="area" position="time*value" size={0} color={fillColor} style={lineStyle} shape="smooth" />
|
|
||||||
<Geom type="line" position="time*value" size={1} color={layout.valueColor} style={lineStyle} shape="smooth" />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderAreaGeom(layout: LayoutResult) {
|
|
||||||
const lineStyle: any = {
|
|
||||||
opacity: 1,
|
|
||||||
fillOpacity: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
const color1 = tinycolor(layout.valueColor)
|
|
||||||
.darken(0)
|
|
||||||
.spin(20)
|
|
||||||
.toRgbString();
|
|
||||||
const color2 = tinycolor(layout.valueColor)
|
|
||||||
.lighten(0)
|
|
||||||
.spin(-20)
|
|
||||||
.toRgbString();
|
|
||||||
|
|
||||||
const fillColor = `l (0) 0:${color1} 1:${color2}`;
|
|
||||||
|
|
||||||
return <Geom type="area" position="time*value" size={0} color={fillColor} style={lineStyle} shape="smooth" />;
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`BigValue Render with basic options should render 1`] = `
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"alignItems": "center",
|
||||||
|
"background": "linear-gradient(120deg, rgb(66, 154, 67), rgb(111, 183, 87))",
|
||||||
|
"borderRadius": "3px",
|
||||||
|
"display": "flex",
|
||||||
|
"flexDirection": "row",
|
||||||
|
"height": "300px",
|
||||||
|
"padding": "12px",
|
||||||
|
"position": "relative",
|
||||||
|
"width": "300px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"alignItems": "center",
|
||||||
|
"display": "flex",
|
||||||
|
"flexDirection": "column",
|
||||||
|
"flexGrow": 1,
|
||||||
|
"justifyContent": "center",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"color": "#EEE",
|
||||||
|
"fontSize": "230px",
|
||||||
|
"fontWeight": 500,
|
||||||
|
"lineHeight": 1.2,
|
||||||
|
"textShadow": "#333 0px 0px 1px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
25
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
151
packages/grafana-ui/src/components/BigValue/renderGraph.tsx
Normal file
151
packages/grafana-ui/src/components/BigValue/renderGraph.tsx
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
import React, { CSSProperties } from 'react';
|
||||||
|
import tinycolor from 'tinycolor2';
|
||||||
|
|
||||||
|
import { Chart, Geom, Guide } from 'bizcharts';
|
||||||
|
import { LayoutResult, LayoutType } from './styles';
|
||||||
|
import { BigValueSparkline, BigValueColorMode } from './BigValue';
|
||||||
|
|
||||||
|
const { DataMarker } = Guide;
|
||||||
|
|
||||||
|
export function renderGraph(layout: LayoutResult, sparkline?: BigValueSparkline) {
|
||||||
|
if (!sparkline || layout.type === LayoutType.WideNoChart || layout.type === LayoutType.StackedNoChart) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = sparkline.data.map(values => {
|
||||||
|
return { time: values[0], value: values[1], name: 'A' };
|
||||||
|
});
|
||||||
|
|
||||||
|
const scales = {
|
||||||
|
time: {
|
||||||
|
type: 'time',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const chartStyles: CSSProperties = {
|
||||||
|
position: 'absolute',
|
||||||
|
};
|
||||||
|
|
||||||
|
// default to line graph
|
||||||
|
const geomRender = getGraphGeom(layout.colorMode);
|
||||||
|
|
||||||
|
if (layout.type === LayoutType.Wide) {
|
||||||
|
// Area chart
|
||||||
|
chartStyles.bottom = 0;
|
||||||
|
chartStyles.right = 0;
|
||||||
|
} else {
|
||||||
|
// need some top padding
|
||||||
|
chartStyles.width = `${layout.chartWidth}px`;
|
||||||
|
chartStyles.height = `${layout.chartHeight}px`;
|
||||||
|
chartStyles.bottom = 0;
|
||||||
|
chartStyles.right = 0;
|
||||||
|
chartStyles.left = 0;
|
||||||
|
chartStyles.right = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Chart
|
||||||
|
height={layout.chartHeight}
|
||||||
|
width={layout.chartWidth}
|
||||||
|
data={data}
|
||||||
|
animate={false}
|
||||||
|
padding={[4, 0, 0, 0]}
|
||||||
|
scale={scales}
|
||||||
|
style={chartStyles}
|
||||||
|
>
|
||||||
|
{geomRender(layout, sparkline)}
|
||||||
|
</Chart>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function getGraphGeom(colorMode: BigValueColorMode) {
|
||||||
|
// background color mode
|
||||||
|
if (colorMode === BigValueColorMode.Background) {
|
||||||
|
return renderAreaGeomOnColoredBackground;
|
||||||
|
}
|
||||||
|
return renderClassicAreaGeom;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderAreaGeomOnColoredBackground(layout: LayoutResult, sparkline: BigValueSparkline) {
|
||||||
|
const lineColor = tinycolor(layout.valueColor)
|
||||||
|
.brighten(40)
|
||||||
|
.toRgbString();
|
||||||
|
|
||||||
|
const lineStyle: any = {
|
||||||
|
stroke: lineColor,
|
||||||
|
lineWidth: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Geom type="area" position="time*value" size={0} color="rgba(255,255,255,0.4)" style={lineStyle} shape="smooth" />
|
||||||
|
<Geom type="line" position="time*value" size={1} color={lineColor} style={lineStyle} shape="smooth" />
|
||||||
|
{highlightPoint(lineColor, sparkline)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function highlightPoint(lineColor: string, sparkline: BigValueSparkline) {
|
||||||
|
if (!sparkline.highlightIndex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pointPos = sparkline.data[sparkline.highlightIndex];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Guide>
|
||||||
|
<DataMarker
|
||||||
|
top
|
||||||
|
position={pointPos}
|
||||||
|
lineLength={0}
|
||||||
|
display={{ point: true }}
|
||||||
|
style={{
|
||||||
|
point: {
|
||||||
|
color: lineColor,
|
||||||
|
stroke: lineColor,
|
||||||
|
r: 2,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Guide>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderClassicAreaGeom(layout: LayoutResult, sparkline: BigValueSparkline) {
|
||||||
|
const lineStyle: any = {
|
||||||
|
opacity: 1,
|
||||||
|
fillOpacity: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
const fillColor = tinycolor(layout.valueColor)
|
||||||
|
.setAlpha(0.2)
|
||||||
|
.toRgbString();
|
||||||
|
lineStyle.stroke = layout.valueColor;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Geom type="area" position="time*value" size={0} color={fillColor} style={lineStyle} shape="smooth" />
|
||||||
|
<Geom type="line" position="time*value" size={1} color={layout.valueColor} style={lineStyle} shape="smooth" />
|
||||||
|
{highlightPoint('#EEE', sparkline)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* function renderAreaGeom(layout: LayoutResult) { */
|
||||||
|
/* const lineStyle: any = { */
|
||||||
|
/* opacity: 1, */
|
||||||
|
/* fillOpacity: 1, */
|
||||||
|
/* }; */
|
||||||
|
/* */
|
||||||
|
/* const color1 = tinycolor(layout.valueColor) */
|
||||||
|
/* .darken(0) */
|
||||||
|
/* .spin(20) */
|
||||||
|
/* .toRgbString(); */
|
||||||
|
/* const color2 = tinycolor(layout.valueColor) */
|
||||||
|
/* .lighten(0) */
|
||||||
|
/* .spin(-20) */
|
||||||
|
/* .toRgbString(); */
|
||||||
|
/* */
|
||||||
|
/* const fillColor = `l (0) 0:${color1} 1:${color2}`; */
|
||||||
|
/* */
|
||||||
|
/* return <Geom type="area" position="time*value" size={0} color={fillColor} style={lineStyle} shape="smooth" />; */
|
||||||
|
/* } */
|
52
packages/grafana-ui/src/components/BigValue/styles.test.tsx
Normal file
52
packages/grafana-ui/src/components/BigValue/styles.test.tsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { Props, BigValueColorMode, BigValueGraphMode } from './BigValue';
|
||||||
|
import { calculateLayout, LayoutType } from './styles';
|
||||||
|
import { getTheme } from '../../themes';
|
||||||
|
|
||||||
|
function getProps(propOverrides?: Partial<Props>): Props {
|
||||||
|
const props: Props = {
|
||||||
|
colorMode: BigValueColorMode.Background,
|
||||||
|
graphMode: BigValueGraphMode.Area,
|
||||||
|
height: 300,
|
||||||
|
width: 300,
|
||||||
|
value: {
|
||||||
|
text: '25',
|
||||||
|
numeric: 25,
|
||||||
|
},
|
||||||
|
sparkline: {
|
||||||
|
data: [
|
||||||
|
[10, 10],
|
||||||
|
[10, 10],
|
||||||
|
],
|
||||||
|
minX: 0,
|
||||||
|
maxX: 100,
|
||||||
|
},
|
||||||
|
theme: getTheme(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.assign(props, propOverrides);
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('BigValue styles', () => {
|
||||||
|
describe('calculateLayout', () => {
|
||||||
|
it('should auto select to stacked layout', () => {
|
||||||
|
const layout = calculateLayout(
|
||||||
|
getProps({
|
||||||
|
width: 300,
|
||||||
|
height: 300,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(layout.type).toBe(LayoutType.Stacked);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should auto select to wide layout', () => {
|
||||||
|
const layout = calculateLayout(
|
||||||
|
getProps({
|
||||||
|
width: 300,
|
||||||
|
height: 100,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(layout.type).toBe(LayoutType.Wide);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
290
packages/grafana-ui/src/components/BigValue/styles.tsx
Normal file
290
packages/grafana-ui/src/components/BigValue/styles.tsx
Normal file
@ -0,0 +1,290 @@
|
|||||||
|
// Libraries
|
||||||
|
import { CSSProperties } from 'react';
|
||||||
|
import tinycolor from 'tinycolor2';
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
import { getColorFromHexRgbOrName, GrafanaTheme } from '@grafana/data';
|
||||||
|
import { calculateFontSize } from '../../utils/measureText';
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import { BigValueColorMode, BigValueGraphMode, Props, BigValueJustifyMode } from './BigValue';
|
||||||
|
|
||||||
|
const LINE_HEIGHT = 1.2;
|
||||||
|
|
||||||
|
export interface LayoutResult {
|
||||||
|
titleFontSize: number;
|
||||||
|
valueFontSize: number;
|
||||||
|
chartHeight: number;
|
||||||
|
chartWidth: number;
|
||||||
|
type: LayoutType;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
colorMode: BigValueColorMode;
|
||||||
|
graphMode: BigValueGraphMode;
|
||||||
|
theme: GrafanaTheme;
|
||||||
|
valueColor: string;
|
||||||
|
panelPadding: number;
|
||||||
|
justifyCenter: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum LayoutType {
|
||||||
|
Stacked,
|
||||||
|
StackedNoChart,
|
||||||
|
Wide,
|
||||||
|
WideNoChart,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shouldJustifyCenter(props: Props) {
|
||||||
|
const { value, justifyMode } = props;
|
||||||
|
if (justifyMode === BigValueJustifyMode.Center) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return (value.title ?? '').length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function calculateLayout(props: Props): LayoutResult {
|
||||||
|
const { width, height, sparkline, colorMode, theme, value, graphMode, alignmentFactors } = props;
|
||||||
|
const useWideLayout = width / height > 2.5;
|
||||||
|
const valueColor = getColorFromHexRgbOrName(value.color || 'green', theme.type);
|
||||||
|
const justifyCenter = shouldJustifyCenter(props);
|
||||||
|
const panelPadding = height > 100 ? 12 : 8;
|
||||||
|
const titleToAlignTo = alignmentFactors ? alignmentFactors.title : value.title;
|
||||||
|
const valueToAlignTo = alignmentFactors ? alignmentFactors.text : value.text;
|
||||||
|
|
||||||
|
const maxTitleFontSize = 30;
|
||||||
|
const maxTextWidth = width - panelPadding * 2;
|
||||||
|
const maxTextHeight = height - panelPadding * 2;
|
||||||
|
|
||||||
|
let layoutType = LayoutType.Stacked;
|
||||||
|
let chartHeight = 0;
|
||||||
|
let chartWidth = 0;
|
||||||
|
let titleHeight = 0;
|
||||||
|
let titleFontSize = 0;
|
||||||
|
let valueFontSize = 14;
|
||||||
|
|
||||||
|
if (useWideLayout) {
|
||||||
|
// Detect auto wide layout type
|
||||||
|
layoutType = height > 50 && !!sparkline ? LayoutType.Wide : LayoutType.WideNoChart;
|
||||||
|
|
||||||
|
// Wide no chart mode
|
||||||
|
if (layoutType === LayoutType.WideNoChart) {
|
||||||
|
const valueWidthPercent = 0.3;
|
||||||
|
|
||||||
|
if (titleToAlignTo && titleToAlignTo.length > 0) {
|
||||||
|
// initial value size
|
||||||
|
valueFontSize = calculateFontSize(valueToAlignTo, maxTextWidth * valueWidthPercent, maxTextHeight, LINE_HEIGHT);
|
||||||
|
// How big can we make the title and still have it fit
|
||||||
|
titleFontSize = calculateFontSize(
|
||||||
|
titleToAlignTo,
|
||||||
|
maxTextWidth * 0.6,
|
||||||
|
maxTextHeight,
|
||||||
|
LINE_HEIGHT,
|
||||||
|
maxTitleFontSize
|
||||||
|
);
|
||||||
|
|
||||||
|
// make sure it's a bit smaller than valueFontSize
|
||||||
|
titleFontSize = Math.min(valueFontSize * 0.7, titleFontSize);
|
||||||
|
titleHeight = titleFontSize * LINE_HEIGHT;
|
||||||
|
} else {
|
||||||
|
// if no title wide
|
||||||
|
valueFontSize = calculateFontSize(valueToAlignTo, maxTextWidth, maxTextHeight, LINE_HEIGHT);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// wide with chart
|
||||||
|
const chartHeightPercent = 0.5;
|
||||||
|
const titleWidthPercent = 0.6;
|
||||||
|
const valueWidthPercent = 1 - titleWidthPercent;
|
||||||
|
const textHeightPercent = 0.4;
|
||||||
|
|
||||||
|
chartWidth = width;
|
||||||
|
chartHeight = height * chartHeightPercent;
|
||||||
|
|
||||||
|
if (titleToAlignTo && titleToAlignTo.length > 0) {
|
||||||
|
titleFontSize = calculateFontSize(
|
||||||
|
titleToAlignTo,
|
||||||
|
maxTextWidth * titleWidthPercent,
|
||||||
|
maxTextHeight * textHeightPercent,
|
||||||
|
LINE_HEIGHT,
|
||||||
|
maxTitleFontSize
|
||||||
|
);
|
||||||
|
titleHeight = titleFontSize * LINE_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
valueFontSize = calculateFontSize(
|
||||||
|
valueToAlignTo,
|
||||||
|
maxTextWidth * valueWidthPercent,
|
||||||
|
maxTextHeight * chartHeightPercent,
|
||||||
|
LINE_HEIGHT
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Stacked layout (title, value, chart)
|
||||||
|
const titleHeightPercent = 0.15;
|
||||||
|
const chartHeightPercent = 0.25;
|
||||||
|
|
||||||
|
// Does a chart fit or exist?
|
||||||
|
if (height < 100 || !sparkline) {
|
||||||
|
layoutType = LayoutType.StackedNoChart;
|
||||||
|
} else {
|
||||||
|
// we have chart
|
||||||
|
chartHeight = height * chartHeightPercent;
|
||||||
|
chartWidth = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (titleToAlignTo && titleToAlignTo.length > 0) {
|
||||||
|
titleFontSize = calculateFontSize(
|
||||||
|
titleToAlignTo,
|
||||||
|
maxTextWidth,
|
||||||
|
height * titleHeightPercent,
|
||||||
|
LINE_HEIGHT,
|
||||||
|
maxTitleFontSize
|
||||||
|
);
|
||||||
|
titleHeight = titleFontSize * LINE_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
valueFontSize = calculateFontSize(
|
||||||
|
valueToAlignTo,
|
||||||
|
maxTextWidth,
|
||||||
|
maxTextHeight - chartHeight - titleHeight,
|
||||||
|
LINE_HEIGHT
|
||||||
|
);
|
||||||
|
// make title fontsize it's a bit smaller than valueFontSize
|
||||||
|
titleFontSize = Math.min(valueFontSize * 0.7, titleFontSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
valueFontSize,
|
||||||
|
titleFontSize,
|
||||||
|
chartHeight,
|
||||||
|
chartWidth,
|
||||||
|
type: layoutType,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
colorMode,
|
||||||
|
graphMode,
|
||||||
|
theme,
|
||||||
|
valueColor,
|
||||||
|
justifyCenter,
|
||||||
|
panelPadding,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTitleStyles(layout: LayoutResult) {
|
||||||
|
const styles: CSSProperties = {
|
||||||
|
fontSize: `${layout.titleFontSize}px`,
|
||||||
|
textShadow: '#333 0px 0px 1px',
|
||||||
|
color: '#EEE',
|
||||||
|
lineHeight: LINE_HEIGHT,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (layout.theme.isLight) {
|
||||||
|
styles.color = 'white';
|
||||||
|
}
|
||||||
|
|
||||||
|
return styles;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getValueStyles(layout: LayoutResult) {
|
||||||
|
const styles: CSSProperties = {
|
||||||
|
fontSize: `${layout.valueFontSize}px`,
|
||||||
|
color: '#EEE',
|
||||||
|
textShadow: '#333 0px 0px 1px',
|
||||||
|
fontWeight: 500,
|
||||||
|
lineHeight: LINE_HEIGHT,
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (layout.colorMode) {
|
||||||
|
case BigValueColorMode.Value:
|
||||||
|
styles.color = layout.valueColor;
|
||||||
|
}
|
||||||
|
return styles;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getValueAndTitleContainerStyles(layout: LayoutResult): CSSProperties {
|
||||||
|
const styles: CSSProperties = {
|
||||||
|
display: 'flex',
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (layout.type) {
|
||||||
|
case LayoutType.Wide:
|
||||||
|
styles.flexDirection = 'row';
|
||||||
|
styles.justifyContent = 'space-between';
|
||||||
|
styles.flexGrow = 1;
|
||||||
|
break;
|
||||||
|
case LayoutType.WideNoChart:
|
||||||
|
styles.flexDirection = 'row';
|
||||||
|
styles.justifyContent = 'space-between';
|
||||||
|
styles.alignItems = 'center';
|
||||||
|
styles.flexGrow = 1;
|
||||||
|
break;
|
||||||
|
case LayoutType.StackedNoChart:
|
||||||
|
styles.flexDirection = 'column';
|
||||||
|
styles.flexGrow = 1;
|
||||||
|
break;
|
||||||
|
case LayoutType.Stacked:
|
||||||
|
default:
|
||||||
|
styles.flexDirection = 'column';
|
||||||
|
styles.justifyContent = 'center';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layout.justifyCenter) {
|
||||||
|
styles.alignItems = 'center';
|
||||||
|
styles.justifyContent = 'center';
|
||||||
|
styles.flexGrow = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return styles;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPanelStyles(layout: LayoutResult) {
|
||||||
|
const panelStyles: CSSProperties = {
|
||||||
|
width: `${layout.width}px`,
|
||||||
|
height: `${layout.height}px`,
|
||||||
|
padding: `${layout.panelPadding}px`,
|
||||||
|
borderRadius: '3px',
|
||||||
|
position: 'relative',
|
||||||
|
display: 'flex',
|
||||||
|
};
|
||||||
|
|
||||||
|
const themeFactor = layout.theme.isDark ? 1 : -0.7;
|
||||||
|
|
||||||
|
switch (layout.colorMode) {
|
||||||
|
case BigValueColorMode.Background:
|
||||||
|
const bgColor2 = tinycolor(layout.valueColor)
|
||||||
|
.darken(15 * themeFactor)
|
||||||
|
.spin(8)
|
||||||
|
.toRgbString();
|
||||||
|
const bgColor3 = tinycolor(layout.valueColor)
|
||||||
|
.darken(5 * themeFactor)
|
||||||
|
.spin(-8)
|
||||||
|
.toRgbString();
|
||||||
|
panelStyles.background = `linear-gradient(120deg, ${bgColor2}, ${bgColor3})`;
|
||||||
|
break;
|
||||||
|
case BigValueColorMode.Value:
|
||||||
|
panelStyles.background = `${layout.theme.colors.dark4}`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (layout.type) {
|
||||||
|
case LayoutType.Stacked:
|
||||||
|
panelStyles.flexDirection = 'column';
|
||||||
|
break;
|
||||||
|
case LayoutType.StackedNoChart:
|
||||||
|
panelStyles.alignItems = 'center';
|
||||||
|
break;
|
||||||
|
case LayoutType.Wide:
|
||||||
|
panelStyles.flexDirection = 'row';
|
||||||
|
panelStyles.justifyContent = 'space-between';
|
||||||
|
break;
|
||||||
|
case LayoutType.WideNoChart:
|
||||||
|
panelStyles.alignItems = 'center';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layout.justifyCenter) {
|
||||||
|
panelStyles.alignItems = 'center';
|
||||||
|
panelStyles.flexDirection = 'row';
|
||||||
|
}
|
||||||
|
|
||||||
|
return panelStyles;
|
||||||
|
}
|
@ -48,14 +48,22 @@ export { Table } from './Table/Table';
|
|||||||
export { TableInputCSV } from './Table/TableInputCSV';
|
export { TableInputCSV } from './Table/TableInputCSV';
|
||||||
|
|
||||||
// Visualizations
|
// Visualizations
|
||||||
export { BigValue, BigValueDisplayMode, BigValueSparkline } from './BigValue/BigValue';
|
export {
|
||||||
|
BigValue,
|
||||||
|
BigValueColorMode,
|
||||||
|
BigValueSparkline,
|
||||||
|
BigValueGraphMode,
|
||||||
|
BigValueJustifyMode,
|
||||||
|
} from './BigValue/BigValue';
|
||||||
|
|
||||||
export { Gauge } from './Gauge/Gauge';
|
export { Gauge } from './Gauge/Gauge';
|
||||||
export { Graph } from './Graph/Graph';
|
export { Graph } from './Graph/Graph';
|
||||||
export { GraphLegend } from './Graph/GraphLegend';
|
export { GraphLegend } from './Graph/GraphLegend';
|
||||||
export { GraphWithLegend } from './Graph/GraphWithLegend';
|
export { GraphWithLegend } from './Graph/GraphWithLegend';
|
||||||
export { BarGauge, BarGaugeAlignmentFactors } from './BarGauge/BarGauge';
|
export { BarGauge } from './BarGauge/BarGauge';
|
||||||
export { GraphTooltipOptions } from './Graph/GraphTooltip/types';
|
export { GraphTooltipOptions } from './Graph/GraphTooltip/types';
|
||||||
export { VizRepeater } from './VizRepeater/VizRepeater';
|
export { VizRepeater } from './VizRepeater/VizRepeater';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
LegendOptions,
|
LegendOptions,
|
||||||
LegendBasicOptions,
|
LegendBasicOptions,
|
||||||
@ -66,6 +74,7 @@ export {
|
|||||||
LegendPlacement,
|
LegendPlacement,
|
||||||
LegendDisplayMode,
|
LegendDisplayMode,
|
||||||
} from './Legend/Legend';
|
} from './Legend/Legend';
|
||||||
|
|
||||||
export { Alert, AlertVariant } from './Alert/Alert';
|
export { Alert, AlertVariant } from './Alert/Alert';
|
||||||
export { GraphSeriesToggler, GraphSeriesTogglerAPI } from './Graph/GraphSeriesToggler';
|
export { GraphSeriesToggler, GraphSeriesTogglerAPI } from './Graph/GraphSeriesToggler';
|
||||||
export { Collapse } from './Collapse/Collapse';
|
export { Collapse } from './Collapse/Collapse';
|
||||||
|
@ -25,3 +25,15 @@ export function measureText(text: string, fontSize: number): TextMetrics {
|
|||||||
cache[cacheKey] = metrics;
|
cache[cacheKey] = metrics;
|
||||||
return metrics;
|
return metrics;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function calculateFontSize(text: string, width: number, height: number, lineHeight: number, maxSize?: number) {
|
||||||
|
// calculate width in 14px
|
||||||
|
const textSize = measureText(text, 14);
|
||||||
|
// how much bigger than 14px can we make it while staying within our width constraints
|
||||||
|
const fontSizeBasedOnWidth = (width / (textSize.width + 2)) * 14;
|
||||||
|
const fontSizeBasedOnHeight = height / lineHeight;
|
||||||
|
|
||||||
|
// final fontSize
|
||||||
|
const optimialSize = Math.min(fontSizeBasedOnHeight, fontSizeBasedOnWidth);
|
||||||
|
return Math.min(optimialSize, maxSize ?? optimialSize);
|
||||||
|
}
|
||||||
|
@ -4,36 +4,23 @@ import React, { PureComponent } from 'react';
|
|||||||
// Services & Utils
|
// Services & Utils
|
||||||
import { config } from 'app/core/config';
|
import { config } from 'app/core/config';
|
||||||
|
|
||||||
import { BarGauge, BarGaugeAlignmentFactors, VizRepeater, DataLinksContextMenu } from '@grafana/ui';
|
import { BarGauge, VizRepeater, DataLinksContextMenu } from '@grafana/ui';
|
||||||
import { BarGaugeOptions } from './types';
|
import { BarGaugeOptions } from './types';
|
||||||
import { getFieldDisplayValues, FieldDisplay, PanelProps } from '@grafana/data';
|
import {
|
||||||
|
getFieldDisplayValues,
|
||||||
|
FieldDisplay,
|
||||||
|
PanelProps,
|
||||||
|
getDisplayValueAlignmentFactors,
|
||||||
|
DisplayValueAlignmentFactors,
|
||||||
|
} from '@grafana/data';
|
||||||
import { getFieldLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers';
|
import { getFieldLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers';
|
||||||
|
|
||||||
export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> {
|
export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> {
|
||||||
findMaximumInput = (values: FieldDisplay[], width: number, height: number): BarGaugeAlignmentFactors => {
|
|
||||||
const info: BarGaugeAlignmentFactors = {
|
|
||||||
title: '',
|
|
||||||
text: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let i = 0; i < values.length; i++) {
|
|
||||||
const v = values[i].display;
|
|
||||||
if (v.text && v.text.length > info.text.length) {
|
|
||||||
info.text = v.text;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (v.title && v.title.length > info.title.length) {
|
|
||||||
info.title = v.title;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return info;
|
|
||||||
};
|
|
||||||
|
|
||||||
renderValue = (
|
renderValue = (
|
||||||
value: FieldDisplay,
|
value: FieldDisplay,
|
||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
alignmentFactors: BarGaugeAlignmentFactors
|
alignmentFactors: DisplayValueAlignmentFactors
|
||||||
): JSX.Element => {
|
): JSX.Element => {
|
||||||
const { options } = this.props;
|
const { options } = this.props;
|
||||||
const { field, display } = value;
|
const { field, display } = value;
|
||||||
@ -87,7 +74,7 @@ export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> {
|
|||||||
return (
|
return (
|
||||||
<VizRepeater
|
<VizRepeater
|
||||||
source={data}
|
source={data}
|
||||||
getAlignmentFactors={this.findMaximumInput}
|
getAlignmentFactors={getDisplayValueAlignmentFactors}
|
||||||
getValues={this.getValues}
|
getValues={this.getValues}
|
||||||
renderValue={this.renderValue}
|
renderValue={this.renderValue}
|
||||||
renderCounter={renderCounter}
|
renderCounter={renderCounter}
|
||||||
|
@ -15,7 +15,8 @@ import {
|
|||||||
import { FieldDisplayOptions, FieldConfig, DataLink, PanelEditorProps } from '@grafana/data';
|
import { FieldDisplayOptions, FieldConfig, DataLink, PanelEditorProps } from '@grafana/data';
|
||||||
|
|
||||||
import { Threshold, ValueMapping } from '@grafana/data';
|
import { Threshold, ValueMapping } from '@grafana/data';
|
||||||
import { BarGaugeOptions, orientationOptions, displayModes } from './types';
|
import { BarGaugeOptions, displayModes } from './types';
|
||||||
|
import { orientationOptions } from '../gauge/types';
|
||||||
import {
|
import {
|
||||||
getDataLinksVariableSuggestions,
|
getDataLinksVariableSuggestions,
|
||||||
getCalculationValueDataLinksVariableSuggestions,
|
getCalculationValueDataLinksVariableSuggestions,
|
||||||
|
@ -12,11 +12,6 @@ export const displayModes: Array<SelectableValue<string>> = [
|
|||||||
{ value: 'basic', label: 'Basic' },
|
{ value: 'basic', label: 'Basic' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const orientationOptions: Array<SelectableValue<VizOrientation>> = [
|
|
||||||
{ value: VizOrientation.Horizontal, label: 'Horizontal' },
|
|
||||||
{ value: VizOrientation.Vertical, label: 'Vertical' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const defaults: BarGaugeOptions = {
|
export const defaults: BarGaugeOptions = {
|
||||||
displayMode: 'lcd',
|
displayMode: 'lcd',
|
||||||
orientation: VizOrientation.Horizontal,
|
orientation: VizOrientation.Horizontal,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { VizOrientation, FieldDisplayOptions } from '@grafana/data';
|
import { VizOrientation, FieldDisplayOptions, SelectableValue } from '@grafana/data';
|
||||||
import { SingleStatBaseOptions } from '@grafana/ui/src/components/SingleStatShared/SingleStatBaseOptions';
|
import { SingleStatBaseOptions } from '@grafana/ui/src/components/SingleStatShared/SingleStatBaseOptions';
|
||||||
import { standardFieldDisplayOptions } from '../stat/types';
|
import { standardFieldDisplayOptions } from '../stat/types';
|
||||||
|
|
||||||
@ -11,6 +11,12 @@ export const standardGaugeFieldOptions: FieldDisplayOptions = {
|
|||||||
...standardFieldDisplayOptions,
|
...standardFieldDisplayOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const orientationOptions: Array<SelectableValue<VizOrientation>> = [
|
||||||
|
{ value: VizOrientation.Auto, label: 'Auto' },
|
||||||
|
{ value: VizOrientation.Horizontal, label: 'Horizontal' },
|
||||||
|
{ value: VizOrientation.Vertical, label: 'Vertical' },
|
||||||
|
];
|
||||||
|
|
||||||
export const defaults: GaugeOptions = {
|
export const defaults: GaugeOptions = {
|
||||||
showThresholdMarkers: true,
|
showThresholdMarkers: true,
|
||||||
showThresholdLabels: false,
|
showThresholdLabels: false,
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
// Libraries
|
|
||||||
import React, { PureComponent } from 'react';
|
|
||||||
|
|
||||||
// Components
|
|
||||||
import { Switch } from '@grafana/ui';
|
|
||||||
|
|
||||||
// Types
|
|
||||||
import { SparklineOptions } from './types';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
options: SparklineOptions;
|
|
||||||
onChange: (options: SparklineOptions) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SparklineEditor extends PureComponent<Props> {
|
|
||||||
onToggleShow = () => this.props.onChange({ ...this.props.options, show: !this.props.options.show });
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { show } = this.props.options;
|
|
||||||
|
|
||||||
return <Switch label="Graph" labelClass="width-8" checked={show} onChange={this.onToggleShow} />;
|
|
||||||
}
|
|
||||||
}
|
|
@ -6,22 +6,41 @@ import { config } from 'app/core/config';
|
|||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { StatPanelOptions } from './types';
|
import { StatPanelOptions } from './types';
|
||||||
import { VizRepeater, BigValue, DataLinksContextMenu, BigValueSparkline } from '@grafana/ui';
|
import { VizRepeater, BigValue, DataLinksContextMenu, BigValueSparkline, BigValueGraphMode } from '@grafana/ui';
|
||||||
import { PanelProps, getFieldDisplayValues, FieldDisplay } from '@grafana/data';
|
|
||||||
|
import {
|
||||||
|
PanelProps,
|
||||||
|
getFieldDisplayValues,
|
||||||
|
FieldDisplay,
|
||||||
|
ReducerID,
|
||||||
|
getDisplayValueAlignmentFactors,
|
||||||
|
DisplayValueAlignmentFactors,
|
||||||
|
VizOrientation,
|
||||||
|
} from '@grafana/data';
|
||||||
|
|
||||||
import { getFieldLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers';
|
import { getFieldLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers';
|
||||||
|
|
||||||
export class StatPanel extends PureComponent<PanelProps<StatPanelOptions>> {
|
export class StatPanel extends PureComponent<PanelProps<StatPanelOptions>> {
|
||||||
renderValue = (value: FieldDisplay, width: number, height: number): JSX.Element => {
|
renderValue = (
|
||||||
|
value: FieldDisplay,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
alignmentFactors: DisplayValueAlignmentFactors
|
||||||
|
): JSX.Element => {
|
||||||
const { timeRange, options } = this.props;
|
const { timeRange, options } = this.props;
|
||||||
let sparkline: BigValueSparkline | undefined;
|
let sparkline: BigValueSparkline | undefined;
|
||||||
|
|
||||||
if (value.sparkline) {
|
if (value.sparkline) {
|
||||||
sparkline = {
|
sparkline = {
|
||||||
...options.sparkline,
|
|
||||||
data: value.sparkline,
|
data: value.sparkline,
|
||||||
minX: timeRange.from.valueOf(),
|
minX: timeRange.from.valueOf(),
|
||||||
maxX: timeRange.to.valueOf(),
|
maxX: timeRange.to.valueOf(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const calc = options.fieldOptions.calcs[0];
|
||||||
|
if (calc === ReducerID.last) {
|
||||||
|
sparkline.highlightIndex = sparkline.data.length - 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -31,7 +50,10 @@ export class StatPanel extends PureComponent<PanelProps<StatPanelOptions>> {
|
|||||||
<BigValue
|
<BigValue
|
||||||
value={value.display}
|
value={value.display}
|
||||||
sparkline={sparkline}
|
sparkline={sparkline}
|
||||||
displayMode={options.displayMode}
|
colorMode={options.colorMode}
|
||||||
|
graphMode={options.graphMode}
|
||||||
|
justifyMode={options.justifyMode}
|
||||||
|
alignmentFactors={alignmentFactors}
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
theme={config.theme}
|
theme={config.theme}
|
||||||
@ -52,22 +74,39 @@ export class StatPanel extends PureComponent<PanelProps<StatPanelOptions>> {
|
|||||||
replaceVariables,
|
replaceVariables,
|
||||||
theme: config.theme,
|
theme: config.theme,
|
||||||
data: data.series,
|
data: data.series,
|
||||||
sparkline: options.sparkline.show,
|
sparkline: options.graphMode !== BigValueGraphMode.None,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { height, width, options, data, renderCounter } = this.props;
|
const { height, options, width, data, renderCounter } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VizRepeater
|
<VizRepeater
|
||||||
getValues={this.getValues}
|
getValues={this.getValues}
|
||||||
|
getAlignmentFactors={getDisplayValueAlignmentFactors}
|
||||||
renderValue={this.renderValue}
|
renderValue={this.renderValue}
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
source={data}
|
source={data}
|
||||||
renderCounter={renderCounter}
|
renderCounter={renderCounter}
|
||||||
orientation={options.orientation}
|
orientation={getOrientation(width, height, options.orientation)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stat panel custom auto orientation
|
||||||
|
*/
|
||||||
|
function getOrientation(width: number, height: number, orientation: VizOrientation): VizOrientation {
|
||||||
|
if (orientation !== VizOrientation.Auto) {
|
||||||
|
return orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width / height > 2) {
|
||||||
|
return VizOrientation.Vertical;
|
||||||
|
} else {
|
||||||
|
return VizOrientation.Horizontal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -15,8 +15,9 @@ import {
|
|||||||
|
|
||||||
import { Threshold, ValueMapping, FieldConfig, DataLink, PanelEditorProps, FieldDisplayOptions } from '@grafana/data';
|
import { Threshold, ValueMapping, FieldConfig, DataLink, PanelEditorProps, FieldDisplayOptions } from '@grafana/data';
|
||||||
|
|
||||||
import { StatPanelOptions, SparklineOptions, displayModes } from './types';
|
import { StatPanelOptions, colorModes, graphModes, justifyModes } from './types';
|
||||||
import { SparklineEditor } from './SparklineEditor';
|
import { orientationOptions } from '../gauge/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getDataLinksVariableSuggestions,
|
getDataLinksVariableSuggestions,
|
||||||
getCalculationValueDataLinksVariableSuggestions,
|
getCalculationValueDataLinksVariableSuggestions,
|
||||||
@ -45,13 +46,10 @@ export class StatPanelEditor extends PureComponent<PanelEditorProps<StatPanelOpt
|
|||||||
fieldOptions,
|
fieldOptions,
|
||||||
});
|
});
|
||||||
|
|
||||||
onSparklineChanged = (sparkline: SparklineOptions) =>
|
onColorModeChanged = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, colorMode: value });
|
||||||
this.props.onOptionsChange({
|
onGraphModeChanged = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, graphMode: value });
|
||||||
...this.props.options,
|
onJustifyModeChanged = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, justifyMode: value });
|
||||||
sparkline,
|
onOrientationChange = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, orientation: value });
|
||||||
});
|
|
||||||
|
|
||||||
onDisplayModeChange = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, displayMode: value });
|
|
||||||
|
|
||||||
onDefaultsChange = (field: FieldConfig) => {
|
onDefaultsChange = (field: FieldConfig) => {
|
||||||
this.onDisplayOptionsChanged({
|
this.onDisplayOptionsChanged({
|
||||||
@ -81,16 +79,45 @@ export class StatPanelEditor extends PureComponent<PanelEditorProps<StatPanelOpt
|
|||||||
<PanelOptionsGroup title="Display">
|
<PanelOptionsGroup title="Display">
|
||||||
<FieldDisplayEditor onChange={this.onDisplayOptionsChanged} value={fieldOptions} labelWidth={8} />
|
<FieldDisplayEditor onChange={this.onDisplayOptionsChanged} value={fieldOptions} labelWidth={8} />
|
||||||
<div className="form-field">
|
<div className="form-field">
|
||||||
<FormLabel width={8}>Display mode</FormLabel>
|
<FormLabel width={8}>Orientation</FormLabel>
|
||||||
<Select
|
<Select
|
||||||
width={12}
|
width={12}
|
||||||
options={displayModes}
|
options={orientationOptions}
|
||||||
defaultValue={displayModes[0]}
|
defaultValue={orientationOptions[0]}
|
||||||
onChange={this.onDisplayModeChange}
|
onChange={this.onOrientationChange}
|
||||||
value={displayModes.find(item => item.value === options.displayMode)}
|
value={orientationOptions.find(item => item.value === options.orientation)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-field">
|
||||||
|
<FormLabel width={8}>Color</FormLabel>
|
||||||
|
<Select
|
||||||
|
width={12}
|
||||||
|
options={colorModes}
|
||||||
|
defaultValue={colorModes[0]}
|
||||||
|
onChange={this.onColorModeChanged}
|
||||||
|
value={colorModes.find(item => item.value === options.colorMode)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-field">
|
||||||
|
<FormLabel width={8}>Graph</FormLabel>
|
||||||
|
<Select
|
||||||
|
width={12}
|
||||||
|
options={graphModes}
|
||||||
|
defaultValue={graphModes[0]}
|
||||||
|
onChange={this.onGraphModeChanged}
|
||||||
|
value={graphModes.find(item => item.value === options.graphMode)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-field">
|
||||||
|
<FormLabel width={8}>Justify</FormLabel>
|
||||||
|
<Select
|
||||||
|
width={12}
|
||||||
|
options={justifyModes}
|
||||||
|
defaultValue={justifyModes[0]}
|
||||||
|
onChange={this.onJustifyModeChanged}
|
||||||
|
value={justifyModes.find(item => item.value === options.justifyMode)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<SparklineEditor options={options.sparkline} onChange={this.onSparklineChanged} />
|
|
||||||
</PanelOptionsGroup>
|
</PanelOptionsGroup>
|
||||||
|
|
||||||
<PanelOptionsGroup title="Field">
|
<PanelOptionsGroup title="Field">
|
||||||
|
@ -1,20 +1,26 @@
|
|||||||
import { SingleStatBaseOptions, BigValueDisplayMode } from '@grafana/ui';
|
import { SingleStatBaseOptions, BigValueColorMode, BigValueGraphMode, BigValueJustifyMode } from '@grafana/ui';
|
||||||
import { VizOrientation, ReducerID, FieldDisplayOptions, SelectableValue } from '@grafana/data';
|
import { VizOrientation, ReducerID, FieldDisplayOptions, SelectableValue } from '@grafana/data';
|
||||||
|
|
||||||
export interface SparklineOptions {
|
|
||||||
show: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Structure copied from angular
|
// Structure copied from angular
|
||||||
export interface StatPanelOptions extends SingleStatBaseOptions {
|
export interface StatPanelOptions extends SingleStatBaseOptions {
|
||||||
sparkline: SparklineOptions;
|
graphMode: BigValueGraphMode;
|
||||||
displayMode: BigValueDisplayMode;
|
colorMode: BigValueColorMode;
|
||||||
|
justifyMode: BigValueJustifyMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const displayModes: Array<SelectableValue<BigValueDisplayMode>> = [
|
export const colorModes: Array<SelectableValue<BigValueColorMode>> = [
|
||||||
{ value: BigValueDisplayMode.Classic, label: 'Classic' },
|
{ value: BigValueColorMode.Value, label: 'Value' },
|
||||||
{ value: BigValueDisplayMode.Vibrant, label: 'Vibrant' },
|
{ value: BigValueColorMode.Background, label: 'Background' },
|
||||||
{ value: BigValueDisplayMode.Vibrant2, label: 'Vibrant 2' },
|
];
|
||||||
|
|
||||||
|
export const graphModes: Array<SelectableValue<BigValueGraphMode>> = [
|
||||||
|
{ value: BigValueGraphMode.None, label: 'None' },
|
||||||
|
{ value: BigValueGraphMode.Area, label: 'Area graph' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const justifyModes: Array<SelectableValue<BigValueJustifyMode>> = [
|
||||||
|
{ value: BigValueJustifyMode.Auto, label: 'Auto' },
|
||||||
|
{ value: BigValueJustifyMode.Center, label: 'Center' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const standardFieldDisplayOptions: FieldDisplayOptions = {
|
export const standardFieldDisplayOptions: FieldDisplayOptions = {
|
||||||
@ -33,10 +39,9 @@ export const standardFieldDisplayOptions: FieldDisplayOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const defaults: StatPanelOptions = {
|
export const defaults: StatPanelOptions = {
|
||||||
sparkline: {
|
graphMode: BigValueGraphMode.Area,
|
||||||
show: true,
|
colorMode: BigValueColorMode.Value,
|
||||||
},
|
justifyMode: BigValueJustifyMode.Auto,
|
||||||
displayMode: BigValueDisplayMode.Vibrant,
|
|
||||||
fieldOptions: standardFieldDisplayOptions,
|
fieldOptions: standardFieldDisplayOptions,
|
||||||
orientation: VizOrientation.Auto,
|
orientation: VizOrientation.Auto,
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user