mirror of
https://github.com/grafana/grafana.git
synced 2025-07-31 07:42:31 +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,
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 20,
|
||||
"h": 2,
|
||||
"w": 24,
|
||||
"x": 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,
|
||||
"interval": "10m",
|
||||
"options": {
|
||||
"colorMode": 0,
|
||||
"displayMode": 2,
|
||||
"colorMode": "background",
|
||||
"fieldOptions": {
|
||||
"calcs": ["mean"],
|
||||
"defaults": {
|
||||
@ -237,60 +62,64 @@
|
||||
"override": {},
|
||||
"values": false
|
||||
},
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"sparkline": {
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
"pluginVersion": "6.5.0-pre",
|
||||
"pluginVersion": "6.6.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"alias": "A longer title",
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk"
|
||||
},
|
||||
{
|
||||
"refId": "B",
|
||||
"scenarioId": "random_walk"
|
||||
},
|
||||
{
|
||||
"alias": "AB",
|
||||
"refId": "C",
|
||||
"scenarioId": "random_walk"
|
||||
},
|
||||
{
|
||||
"alias": "CPU",
|
||||
"refId": "D",
|
||||
"scenarioId": "random_walk"
|
||||
},
|
||||
{
|
||||
"alias": "Google",
|
||||
"labels": "",
|
||||
"refId": "E",
|
||||
"scenarioId": "random_walk"
|
||||
},
|
||||
{
|
||||
"alias": "Even longer road title",
|
||||
"refId": "F",
|
||||
"scenarioId": "random_walk"
|
||||
},
|
||||
{
|
||||
"alias": "Why does it have to",
|
||||
"refId": "G",
|
||||
"scenarioId": "random_walk"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Panel Title",
|
||||
"title": "",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 20,
|
||||
"h": 5,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 10
|
||||
"y": 2
|
||||
},
|
||||
"id": 3,
|
||||
"id": 10,
|
||||
"interval": "10m",
|
||||
"options": {
|
||||
"colorMode": 0,
|
||||
"displayMode": 3,
|
||||
"colorMode": "background",
|
||||
"fieldOptions": {
|
||||
"calcs": ["mean"],
|
||||
"defaults": {
|
||||
@ -319,43 +148,48 @@
|
||||
"value": 80
|
||||
}
|
||||
],
|
||||
"unit": "percent"
|
||||
"unit": "areaM2"
|
||||
},
|
||||
"override": {},
|
||||
"values": false
|
||||
},
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"sparkline": {
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
"pluginVersion": "6.5.0-pre",
|
||||
"pluginVersion": "6.6.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"alias": "AB",
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk"
|
||||
},
|
||||
{
|
||||
"refId": "B",
|
||||
"scenarioId": "random_walk"
|
||||
},
|
||||
{
|
||||
"alias": "CPU",
|
||||
"labels": "",
|
||||
"refId": "C",
|
||||
"scenarioId": "random_walk"
|
||||
},
|
||||
{
|
||||
"alias": "Longer title",
|
||||
"refId": "D",
|
||||
"scenarioId": "random_walk"
|
||||
},
|
||||
{
|
||||
"alias": "Even longer title",
|
||||
"refId": "E",
|
||||
"scenarioId": "random_walk"
|
||||
},
|
||||
{
|
||||
"alias": "Outside",
|
||||
"refId": "F",
|
||||
"scenarioId": "random_walk"
|
||||
},
|
||||
{
|
||||
"alias": "Inside",
|
||||
"refId": "G",
|
||||
"scenarioId": "random_walk"
|
||||
}
|
||||
@ -368,16 +202,15 @@
|
||||
{
|
||||
"datasource": null,
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 20,
|
||||
"h": 5,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 16
|
||||
"y": 7
|
||||
},
|
||||
"id": 4,
|
||||
"id": 11,
|
||||
"interval": "10m",
|
||||
"options": {
|
||||
"colorMode": 0,
|
||||
"displayMode": 0,
|
||||
"colorMode": "value",
|
||||
"fieldOptions": {
|
||||
"calcs": ["mean"],
|
||||
"defaults": {
|
||||
@ -411,12 +244,99 @@
|
||||
"override": {},
|
||||
"values": false
|
||||
},
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"sparkline": {
|
||||
"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": [
|
||||
{
|
||||
"refId": "A",
|
||||
@ -455,16 +375,15 @@
|
||||
{
|
||||
"datasource": null,
|
||||
"gridPos": {
|
||||
"h": 21,
|
||||
"h": 11,
|
||||
"w": 4,
|
||||
"x": 20,
|
||||
"y": 20
|
||||
"x": 6,
|
||||
"y": 12
|
||||
},
|
||||
"id": 9,
|
||||
"id": 8,
|
||||
"interval": "10m",
|
||||
"options": {
|
||||
"colorMode": 0,
|
||||
"displayMode": 0,
|
||||
"colorMode": "background",
|
||||
"fieldOptions": {
|
||||
"calcs": ["mean"],
|
||||
"defaults": {
|
||||
@ -498,12 +417,14 @@
|
||||
"override": {},
|
||||
"values": false
|
||||
},
|
||||
"graphMode": "line",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"sparkline": {
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
"pluginVersion": "6.5.0-pre",
|
||||
"pluginVersion": "6.6.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
@ -542,16 +463,15 @@
|
||||
{
|
||||
"datasource": null,
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 20,
|
||||
"x": 0,
|
||||
"y": 22
|
||||
"h": 9,
|
||||
"w": 8,
|
||||
"x": 10,
|
||||
"y": 12
|
||||
},
|
||||
"id": 5,
|
||||
"id": 12,
|
||||
"interval": "10m",
|
||||
"options": {
|
||||
"colorMode": 0,
|
||||
"displayMode": 1,
|
||||
"colorMode": "background",
|
||||
"fieldOptions": {
|
||||
"calcs": ["mean"],
|
||||
"defaults": {
|
||||
@ -585,12 +505,14 @@
|
||||
"override": {},
|
||||
"values": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"graphMode": "line",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "horizontal",
|
||||
"sparkline": {
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
"pluginVersion": "6.5.0-pre",
|
||||
"pluginVersion": "6.6.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
@ -627,7 +549,7 @@
|
||||
"type": "stat"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 20,
|
||||
"schemaVersion": 21,
|
||||
"style": "dark",
|
||||
"tags": ["gdev", "panel-tests"],
|
||||
"templating": {
|
||||
@ -643,5 +565,5 @@
|
||||
"timezone": "",
|
||||
"title": "Panel Tests - Stat",
|
||||
"uid": "jWWHNJpWz",
|
||||
"version": 6
|
||||
"version": 25
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import { FieldConfig, DataFrame, FieldType } from '../types/dataFrame';
|
||||
import { InterpolateFunction } from '../types/panel';
|
||||
import { DataFrameView } from '../dataframe/DataFrameView';
|
||||
import { GraphSeriesValue } from '../types/graph';
|
||||
import { DisplayValue } from '../types/displayValue';
|
||||
import { DisplayValue, DisplayValueAlignmentFactors } from '../types/displayValue';
|
||||
import { GrafanaTheme } from '../types/theme';
|
||||
import { ReducerID, reduceField } from '../transformations/fieldReducer';
|
||||
import { ScopedVars } from '../types/ScopedVars';
|
||||
@ -21,6 +21,7 @@ export interface FieldDisplayOptions {
|
||||
defaults: FieldConfig; // Use these values unless otherwise stated
|
||||
override: FieldConfig; // Set these values regardless of the source
|
||||
}
|
||||
|
||||
// TODO: use built in variables, same as for data links?
|
||||
export const VAR_SERIES_NAME = '__series.name';
|
||||
export const VAR_FIELD_NAME = '__field.name';
|
||||
@ -278,3 +279,22 @@ export function getFieldProperties(...props: FieldConfig[]): FieldConfig {
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 interface DecimalInfo {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { DisplayValue } from './displayValue';
|
||||
import { Field } from './dataFrame';
|
||||
|
||||
export interface YAxis {
|
||||
index: number;
|
||||
min?: number;
|
||||
|
@ -1,11 +1,17 @@
|
||||
// Library
|
||||
import React, { PureComponent, CSSProperties, ReactNode } from 'react';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { Threshold, TimeSeriesValue, getActiveThreshold, DisplayValue } from '@grafana/data';
|
||||
import {
|
||||
Threshold,
|
||||
TimeSeriesValue,
|
||||
getActiveThreshold,
|
||||
DisplayValue,
|
||||
DisplayValueAlignmentFactors,
|
||||
} from '@grafana/data';
|
||||
|
||||
// Utils
|
||||
import { getColorFromHexRgbOrName } from '@grafana/data';
|
||||
import { measureText } from '../../utils/measureText';
|
||||
import { measureText, calculateFontSize } from '../../utils/measureText';
|
||||
|
||||
// Types
|
||||
import { VizOrientation } from '@grafana/data';
|
||||
@ -19,18 +25,6 @@ const TITLE_LINE_HEIGHT = 1.5;
|
||||
const VALUE_LINE_HEIGHT = 1;
|
||||
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 {
|
||||
height: number;
|
||||
width: number;
|
||||
@ -43,7 +37,7 @@ export interface Props extends Themeable {
|
||||
displayMode: 'basic' | 'lcd' | 'gradient';
|
||||
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||
className?: string;
|
||||
alignmentFactors?: BarGaugeAlignmentFactors;
|
||||
alignmentFactors?: DisplayValueAlignmentFactors;
|
||||
}
|
||||
|
||||
export class BarGauge extends PureComponent<Props> {
|
||||
@ -537,14 +531,6 @@ function getValueStyles(
|
||||
textWidth -= VALUE_LEFT_PADDING;
|
||||
}
|
||||
|
||||
// calculate width in 14px
|
||||
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';
|
||||
|
||||
valueStyles.fontSize = calculateFontSize(value, textWidth, height, VALUE_LINE_HEIGHT) + 'px';
|
||||
return valueStyles;
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ exports[`BarGauge Render with basic options should render 1`] = `
|
||||
"alignItems": "center",
|
||||
"color": "#73BF69",
|
||||
"display": "flex",
|
||||
"fontSize": "175.0000px",
|
||||
"fontSize": "175px",
|
||||
"height": "300px",
|
||||
"justifyContent": "flex-start",
|
||||
"lineHeight": 1,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { text } from '@storybook/addon-knobs';
|
||||
import { BigValue, BigValueDisplayMode } from './BigValue';
|
||||
import { BigValue, BigValueColorMode, BigValueGraphMode } from './BigValue';
|
||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||
import { renderComponentWithTheme } from '../../utils/storybook/withTheme';
|
||||
|
||||
@ -16,20 +16,22 @@ const BigValueStories = storiesOf('UI/BigValue', module);
|
||||
BigValueStories.addDecorator(withCenteredStory);
|
||||
|
||||
interface StoryOptions {
|
||||
mode: BigValueDisplayMode;
|
||||
colorMode: BigValueColorMode;
|
||||
graphMode: BigValueGraphMode;
|
||||
width?: number;
|
||||
height?: number;
|
||||
noSparkline?: boolean;
|
||||
}
|
||||
|
||||
function addStoryForMode(options: StoryOptions) {
|
||||
BigValueStories.add(`Mode: ${BigValueDisplayMode[options.mode]}`, () => {
|
||||
BigValueStories.add(`Color: ${options.colorMode}`, () => {
|
||||
const { value, title } = getKnobs();
|
||||
|
||||
return renderComponentWithTheme(BigValue, {
|
||||
width: options.width || 400,
|
||||
height: options.height || 300,
|
||||
displayMode: options.mode,
|
||||
colorMode: options.colorMode,
|
||||
graphMode: options.graphMode,
|
||||
value: {
|
||||
text: value,
|
||||
numeric: 5022,
|
||||
@ -52,7 +54,5 @@ function addStoryForMode(options: StoryOptions) {
|
||||
});
|
||||
}
|
||||
|
||||
addStoryForMode({ mode: BigValueDisplayMode.Classic });
|
||||
addStoryForMode({ mode: BigValueDisplayMode.Classic2 });
|
||||
addStoryForMode({ mode: BigValueDisplayMode.Vibrant });
|
||||
addStoryForMode({ mode: BigValueDisplayMode.Vibrant2 });
|
||||
addStoryForMode({ colorMode: BigValueColorMode.Value, graphMode: BigValueGraphMode.Area });
|
||||
addStoryForMode({ colorMode: BigValueColorMode.Background, graphMode: BigValueGraphMode.Line });
|
||||
|
@ -1,17 +1,14 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { BigValue, Props, BigValueDisplayMode } from './BigValue';
|
||||
import { getTheme } from '../../themes/index';
|
||||
import { BigValue, Props, BigValueColorMode, BigValueGraphMode } from './BigValue';
|
||||
import { getTheme } from '../../themes';
|
||||
|
||||
jest.mock('jquery', () => ({
|
||||
plot: jest.fn(),
|
||||
}));
|
||||
|
||||
const setup = (propOverrides?: object) => {
|
||||
function getProps(propOverrides?: Partial<Props>): Props {
|
||||
const props: Props = {
|
||||
colorMode: BigValueColorMode.Background,
|
||||
graphMode: BigValueGraphMode.Line,
|
||||
height: 300,
|
||||
width: 300,
|
||||
displayMode: BigValueDisplayMode.Classic,
|
||||
value: {
|
||||
text: '25',
|
||||
numeric: 25,
|
||||
@ -20,7 +17,11 @@ const setup = (propOverrides?: object) => {
|
||||
};
|
||||
|
||||
Object.assign(props, propOverrides);
|
||||
return props;
|
||||
}
|
||||
|
||||
const setup = (propOverrides?: object) => {
|
||||
const props = getProps(propOverrides);
|
||||
const wrapper = shallow(<BigValue {...props} />);
|
||||
const instance = wrapper.instance() as BigValue;
|
||||
|
||||
@ -30,10 +31,11 @@ const setup = (propOverrides?: object) => {
|
||||
};
|
||||
};
|
||||
|
||||
describe('Render SingleStat with basic options', () => {
|
||||
it('should render', () => {
|
||||
const { wrapper } = setup();
|
||||
expect(wrapper).toBeDefined();
|
||||
// expect(wrapper).toMatchSnapshot();
|
||||
describe('BigValue', () => {
|
||||
describe('Render with basic options', () => {
|
||||
it('should render', () => {
|
||||
const { wrapper } = setup();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,26 +1,39 @@
|
||||
// Library
|
||||
import React, { PureComponent, CSSProperties } from 'react';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { Chart, Geom } from 'bizcharts';
|
||||
import { DisplayValue } from '@grafana/data';
|
||||
|
||||
// Utils
|
||||
import { getColorFromHexRgbOrName, GrafanaTheme } from '@grafana/data';
|
||||
import React, { PureComponent } from 'react';
|
||||
import { DisplayValue, GraphSeriesValue, DisplayValueAlignmentFactors } from '@grafana/data';
|
||||
|
||||
// Types
|
||||
import { Themeable } from '../../types';
|
||||
import {
|
||||
calculateLayout,
|
||||
getPanelStyles,
|
||||
getValueAndTitleContainerStyles,
|
||||
getValueStyles,
|
||||
getTitleStyles,
|
||||
} from './styles';
|
||||
import { renderGraph } from './renderGraph';
|
||||
|
||||
export interface BigValueSparkline {
|
||||
data: any[][];
|
||||
data: GraphSeriesValue[][];
|
||||
minX: number;
|
||||
maxX: number;
|
||||
highlightIndex?: number;
|
||||
}
|
||||
|
||||
export enum BigValueDisplayMode {
|
||||
Classic,
|
||||
Classic2,
|
||||
Vibrant,
|
||||
Vibrant2,
|
||||
export enum BigValueColorMode {
|
||||
Value = 'value',
|
||||
Background = 'background',
|
||||
}
|
||||
|
||||
export enum BigValueGraphMode {
|
||||
None = 'none',
|
||||
Line = 'line',
|
||||
Area = 'area',
|
||||
}
|
||||
|
||||
export enum BigValueJustifyMode {
|
||||
Auto = 'auto',
|
||||
Center = 'center',
|
||||
}
|
||||
|
||||
export interface Props extends Themeable {
|
||||
@ -30,10 +43,17 @@ export interface Props extends Themeable {
|
||||
sparkline?: BigValueSparkline;
|
||||
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||
className?: string;
|
||||
displayMode: BigValueDisplayMode;
|
||||
colorMode: BigValueColorMode;
|
||||
graphMode: BigValueGraphMode;
|
||||
justifyMode?: BigValueJustifyMode;
|
||||
alignmentFactors?: DisplayValueAlignmentFactors;
|
||||
}
|
||||
|
||||
export class BigValue extends PureComponent<Props> {
|
||||
static defaultProps: Partial<Props> = {
|
||||
justifyMode: BigValueJustifyMode.Auto,
|
||||
};
|
||||
|
||||
render() {
|
||||
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 style={valueAndTitleContainerStyles}>
|
||||
{value.title && <div style={titleStyles}>{value.title}</div>}
|
||||
<div style={valueStyles}>{value.text}</div>
|
||||
<div style={valueStyles}>{renderValueWithSmallerUnit(value.text, layout.valueFontSize)}</div>
|
||||
</div>
|
||||
{renderGraph(layout, sparkline)}
|
||||
</div>
|
||||
@ -55,360 +75,18 @@ export class BigValue extends PureComponent<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
const MIN_VALUE_FONT_SIZE = 20;
|
||||
const MAX_VALUE_FONT_SIZE = 50;
|
||||
const MIN_TITLE_FONT_SIZE = 14;
|
||||
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;
|
||||
function renderValueWithSmallerUnit(value: string, fontSize: number) {
|
||||
const valueParts = value.split(' ');
|
||||
const unitSize = `${fontSize * 0.7}px`;
|
||||
|
||||
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
|
||||
if (valueParts.length === 2) {
|
||||
return (
|
||||
<>
|
||||
{valueParts[0]}
|
||||
<span style={{ fontSize: unitSize, paddingLeft: '2px' }}>{valueParts[1]}</span>
|
||||
</>
|
||||
);
|
||||
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,
|
||||
};
|
||||
|
||||
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="white" style={lineStyle} shape="smooth" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function renderClassicAreaGeom(layout: LayoutResult) {
|
||||
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" />;
|
||||
return value;
|
||||
}
|
||||
|
@ -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';
|
||||
|
||||
// Visualizations
|
||||
export { BigValue, BigValueDisplayMode, BigValueSparkline } from './BigValue/BigValue';
|
||||
export {
|
||||
BigValue,
|
||||
BigValueColorMode,
|
||||
BigValueSparkline,
|
||||
BigValueGraphMode,
|
||||
BigValueJustifyMode,
|
||||
} from './BigValue/BigValue';
|
||||
|
||||
export { Gauge } from './Gauge/Gauge';
|
||||
export { Graph } from './Graph/Graph';
|
||||
export { GraphLegend } from './Graph/GraphLegend';
|
||||
export { GraphWithLegend } from './Graph/GraphWithLegend';
|
||||
export { BarGauge, BarGaugeAlignmentFactors } from './BarGauge/BarGauge';
|
||||
export { BarGauge } from './BarGauge/BarGauge';
|
||||
export { GraphTooltipOptions } from './Graph/GraphTooltip/types';
|
||||
export { VizRepeater } from './VizRepeater/VizRepeater';
|
||||
|
||||
export {
|
||||
LegendOptions,
|
||||
LegendBasicOptions,
|
||||
@ -66,6 +74,7 @@ export {
|
||||
LegendPlacement,
|
||||
LegendDisplayMode,
|
||||
} from './Legend/Legend';
|
||||
|
||||
export { Alert, AlertVariant } from './Alert/Alert';
|
||||
export { GraphSeriesToggler, GraphSeriesTogglerAPI } from './Graph/GraphSeriesToggler';
|
||||
export { Collapse } from './Collapse/Collapse';
|
||||
|
@ -25,3 +25,15 @@ export function measureText(text: string, fontSize: number): TextMetrics {
|
||||
cache[cacheKey] = 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
|
||||
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 { getFieldDisplayValues, FieldDisplay, PanelProps } from '@grafana/data';
|
||||
import {
|
||||
getFieldDisplayValues,
|
||||
FieldDisplay,
|
||||
PanelProps,
|
||||
getDisplayValueAlignmentFactors,
|
||||
DisplayValueAlignmentFactors,
|
||||
} from '@grafana/data';
|
||||
import { getFieldLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers';
|
||||
|
||||
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 = (
|
||||
value: FieldDisplay,
|
||||
width: number,
|
||||
height: number,
|
||||
alignmentFactors: BarGaugeAlignmentFactors
|
||||
alignmentFactors: DisplayValueAlignmentFactors
|
||||
): JSX.Element => {
|
||||
const { options } = this.props;
|
||||
const { field, display } = value;
|
||||
@ -87,7 +74,7 @@ export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> {
|
||||
return (
|
||||
<VizRepeater
|
||||
source={data}
|
||||
getAlignmentFactors={this.findMaximumInput}
|
||||
getAlignmentFactors={getDisplayValueAlignmentFactors}
|
||||
getValues={this.getValues}
|
||||
renderValue={this.renderValue}
|
||||
renderCounter={renderCounter}
|
||||
|
@ -15,7 +15,8 @@ import {
|
||||
import { FieldDisplayOptions, FieldConfig, DataLink, PanelEditorProps } 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 {
|
||||
getDataLinksVariableSuggestions,
|
||||
getCalculationValueDataLinksVariableSuggestions,
|
||||
|
@ -12,11 +12,6 @@ export const displayModes: Array<SelectableValue<string>> = [
|
||||
{ value: 'basic', label: 'Basic' },
|
||||
];
|
||||
|
||||
export const orientationOptions: Array<SelectableValue<VizOrientation>> = [
|
||||
{ value: VizOrientation.Horizontal, label: 'Horizontal' },
|
||||
{ value: VizOrientation.Vertical, label: 'Vertical' },
|
||||
];
|
||||
|
||||
export const defaults: BarGaugeOptions = {
|
||||
displayMode: 'lcd',
|
||||
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 { standardFieldDisplayOptions } from '../stat/types';
|
||||
|
||||
@ -11,6 +11,12 @@ export const standardGaugeFieldOptions: FieldDisplayOptions = {
|
||||
...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 = {
|
||||
showThresholdMarkers: true,
|
||||
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
|
||||
import { StatPanelOptions } from './types';
|
||||
import { VizRepeater, BigValue, DataLinksContextMenu, BigValueSparkline } from '@grafana/ui';
|
||||
import { PanelProps, getFieldDisplayValues, FieldDisplay } from '@grafana/data';
|
||||
import { VizRepeater, BigValue, DataLinksContextMenu, BigValueSparkline, BigValueGraphMode } from '@grafana/ui';
|
||||
|
||||
import {
|
||||
PanelProps,
|
||||
getFieldDisplayValues,
|
||||
FieldDisplay,
|
||||
ReducerID,
|
||||
getDisplayValueAlignmentFactors,
|
||||
DisplayValueAlignmentFactors,
|
||||
VizOrientation,
|
||||
} from '@grafana/data';
|
||||
|
||||
import { getFieldLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers';
|
||||
|
||||
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;
|
||||
let sparkline: BigValueSparkline | undefined;
|
||||
|
||||
if (value.sparkline) {
|
||||
sparkline = {
|
||||
...options.sparkline,
|
||||
data: value.sparkline,
|
||||
minX: timeRange.from.valueOf(),
|
||||
maxX: timeRange.to.valueOf(),
|
||||
};
|
||||
|
||||
const calc = options.fieldOptions.calcs[0];
|
||||
if (calc === ReducerID.last) {
|
||||
sparkline.highlightIndex = sparkline.data.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@ -31,7 +50,10 @@ export class StatPanel extends PureComponent<PanelProps<StatPanelOptions>> {
|
||||
<BigValue
|
||||
value={value.display}
|
||||
sparkline={sparkline}
|
||||
displayMode={options.displayMode}
|
||||
colorMode={options.colorMode}
|
||||
graphMode={options.graphMode}
|
||||
justifyMode={options.justifyMode}
|
||||
alignmentFactors={alignmentFactors}
|
||||
width={width}
|
||||
height={height}
|
||||
theme={config.theme}
|
||||
@ -52,22 +74,39 @@ export class StatPanel extends PureComponent<PanelProps<StatPanelOptions>> {
|
||||
replaceVariables,
|
||||
theme: config.theme,
|
||||
data: data.series,
|
||||
sparkline: options.sparkline.show,
|
||||
sparkline: options.graphMode !== BigValueGraphMode.None,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { height, width, options, data, renderCounter } = this.props;
|
||||
const { height, options, width, data, renderCounter } = this.props;
|
||||
|
||||
return (
|
||||
<VizRepeater
|
||||
getValues={this.getValues}
|
||||
getAlignmentFactors={getDisplayValueAlignmentFactors}
|
||||
renderValue={this.renderValue}
|
||||
width={width}
|
||||
height={height}
|
||||
source={data}
|
||||
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 { StatPanelOptions, SparklineOptions, displayModes } from './types';
|
||||
import { SparklineEditor } from './SparklineEditor';
|
||||
import { StatPanelOptions, colorModes, graphModes, justifyModes } from './types';
|
||||
import { orientationOptions } from '../gauge/types';
|
||||
|
||||
import {
|
||||
getDataLinksVariableSuggestions,
|
||||
getCalculationValueDataLinksVariableSuggestions,
|
||||
@ -45,13 +46,10 @@ export class StatPanelEditor extends PureComponent<PanelEditorProps<StatPanelOpt
|
||||
fieldOptions,
|
||||
});
|
||||
|
||||
onSparklineChanged = (sparkline: SparklineOptions) =>
|
||||
this.props.onOptionsChange({
|
||||
...this.props.options,
|
||||
sparkline,
|
||||
});
|
||||
|
||||
onDisplayModeChange = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, displayMode: value });
|
||||
onColorModeChanged = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, colorMode: value });
|
||||
onGraphModeChanged = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, graphMode: value });
|
||||
onJustifyModeChanged = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, justifyMode: value });
|
||||
onOrientationChange = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, orientation: value });
|
||||
|
||||
onDefaultsChange = (field: FieldConfig) => {
|
||||
this.onDisplayOptionsChanged({
|
||||
@ -81,16 +79,45 @@ export class StatPanelEditor extends PureComponent<PanelEditorProps<StatPanelOpt
|
||||
<PanelOptionsGroup title="Display">
|
||||
<FieldDisplayEditor onChange={this.onDisplayOptionsChanged} value={fieldOptions} labelWidth={8} />
|
||||
<div className="form-field">
|
||||
<FormLabel width={8}>Display mode</FormLabel>
|
||||
<FormLabel width={8}>Orientation</FormLabel>
|
||||
<Select
|
||||
width={12}
|
||||
options={displayModes}
|
||||
defaultValue={displayModes[0]}
|
||||
onChange={this.onDisplayModeChange}
|
||||
value={displayModes.find(item => item.value === options.displayMode)}
|
||||
options={orientationOptions}
|
||||
defaultValue={orientationOptions[0]}
|
||||
onChange={this.onOrientationChange}
|
||||
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>
|
||||
<SparklineEditor options={options.sparkline} onChange={this.onSparklineChanged} />
|
||||
</PanelOptionsGroup>
|
||||
|
||||
<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';
|
||||
|
||||
export interface SparklineOptions {
|
||||
show: boolean;
|
||||
}
|
||||
|
||||
// Structure copied from angular
|
||||
export interface StatPanelOptions extends SingleStatBaseOptions {
|
||||
sparkline: SparklineOptions;
|
||||
displayMode: BigValueDisplayMode;
|
||||
graphMode: BigValueGraphMode;
|
||||
colorMode: BigValueColorMode;
|
||||
justifyMode: BigValueJustifyMode;
|
||||
}
|
||||
|
||||
export const displayModes: Array<SelectableValue<BigValueDisplayMode>> = [
|
||||
{ value: BigValueDisplayMode.Classic, label: 'Classic' },
|
||||
{ value: BigValueDisplayMode.Vibrant, label: 'Vibrant' },
|
||||
{ value: BigValueDisplayMode.Vibrant2, label: 'Vibrant 2' },
|
||||
export const colorModes: Array<SelectableValue<BigValueColorMode>> = [
|
||||
{ value: BigValueColorMode.Value, label: 'Value' },
|
||||
{ value: BigValueColorMode.Background, label: 'Background' },
|
||||
];
|
||||
|
||||
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 = {
|
||||
@ -33,10 +39,9 @@ export const standardFieldDisplayOptions: FieldDisplayOptions = {
|
||||
};
|
||||
|
||||
export const defaults: StatPanelOptions = {
|
||||
sparkline: {
|
||||
show: true,
|
||||
},
|
||||
displayMode: BigValueDisplayMode.Vibrant,
|
||||
graphMode: BigValueGraphMode.Area,
|
||||
colorMode: BigValueColorMode.Value,
|
||||
justifyMode: BigValueJustifyMode.Auto,
|
||||
fieldOptions: standardFieldDisplayOptions,
|
||||
orientation: VizOrientation.Auto,
|
||||
};
|
||||
|
Reference in New Issue
Block a user