From 81dd57524d79813bd94c4570e61cef63d9f1248b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 4 Oct 2019 12:01:42 +0200 Subject: [PATCH] Panels: Progress on new singlestat / BigValue (#19374) * POC: friday hack * exploring new singlestat styles * minor changes * Testing bizcharts * style tweaks * Updated * minor progress * updated * Updated layout handling * Updated editor * added editor options * adding mode * progress on new display mode * tweaks * Added classic style * Added final mode * Minor tweak * tweaks * minor tweak * Singlestat: Adjust colors for light theme * fixed build issues with bizcharts * fixed typescript issue * updated snapshot * Added demo dashboard --- .../panel-stat/panel-stat-tests.json | 647 ++++++++++++++++++ packages/grafana-ui/package.json | 1 + packages/grafana-ui/rollup.config.ts | 1 + .../components/BigValue/BigValue.story.tsx | 58 +- .../src/components/BigValue/BigValue.test.tsx | 5 +- .../src/components/BigValue/BigValue.tsx | 523 ++++++++++---- .../ThresholdsEditor.test.tsx.snap | 4 + packages/grafana-ui/src/components/index.ts | 2 +- packages/grafana-ui/src/themes/dark.ts | 2 + packages/grafana-ui/src/themes/light.ts | 2 + packages/grafana-ui/src/types/theme.ts | 2 + .../dashboard/state/PanelQueryRunner.ts | 1 + .../panel/singlestat2/ColoringEditor.tsx | 63 -- .../panel/singlestat2/FontSizeEditor.tsx | 70 -- .../panel/singlestat2/SingleStatEditor.tsx | 38 +- .../panel/singlestat2/SingleStatPanel.tsx | 8 +- .../panel/singlestat2/SparklineEditor.tsx | 18 +- public/app/plugins/panel/singlestat2/types.ts | 41 +- public/sass/base/_type.scss | 4 + public/test/jest-setup.ts | 3 +- yarn.lock | 240 ++++++- 21 files changed, 1377 insertions(+), 356 deletions(-) create mode 100644 devenv/dev-dashboards/panel-stat/panel-stat-tests.json delete mode 100644 public/app/plugins/panel/singlestat2/ColoringEditor.tsx delete mode 100644 public/app/plugins/panel/singlestat2/FontSizeEditor.tsx diff --git a/devenv/dev-dashboards/panel-stat/panel-stat-tests.json b/devenv/dev-dashboards/panel-stat/panel-stat-tests.json new file mode 100644 index 00000000000..50b02a4caa5 --- /dev/null +++ b/devenv/dev-dashboards/panel-stat/panel-stat-tests.json @@ -0,0 +1,647 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "datasource": null, + "gridPos": { + "h": 7, + "w": 20, + "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": "singlestat2" + }, + { + "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": "singlestat2" + }, + { + "datasource": null, + "gridPos": { + "h": 3, + "w": 20, + "x": 0, + "y": 7 + }, + "id": 6, + "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": "singlestat2" + }, + { + "datasource": null, + "gridPos": { + "h": 6, + "w": 20, + "x": 0, + "y": 10 + }, + "id": 3, + "interval": "10m", + "options": { + "colorMode": 0, + "displayMode": 3, + "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": "singlestat2" + }, + { + "datasource": null, + "gridPos": { + "h": 6, + "w": 20, + "x": 0, + "y": 16 + }, + "id": 4, + "interval": "10m", + "options": { + "colorMode": 0, + "displayMode": 0, + "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": "singlestat2" + }, + { + "datasource": null, + "gridPos": { + "h": 21, + "w": 4, + "x": 20, + "y": 20 + }, + "id": 9, + "interval": "10m", + "options": { + "colorMode": 0, + "displayMode": 0, + "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": "singlestat2" + }, + { + "datasource": null, + "gridPos": { + "h": 6, + "w": 20, + "x": 0, + "y": 22 + }, + "id": 5, + "interval": "10m", + "options": { + "colorMode": 0, + "displayMode": 1, + "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": "singlestat2" + } + ], + "schemaVersion": 20, + "style": "dark", + "tags": ["gdev", "panel-tests"], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] + }, + "timezone": "", + "title": "Panel Tests - Stat", + "uid": "jWWHNJpWz", + "version": 6 +} diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 51cc6442600..e7e6286941f 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -29,6 +29,7 @@ "@grafana/slate-react": "0.22.9-grafana", "@torkelo/react-select": "2.1.1", "@types/react-color": "2.17.0", + "bizcharts": "^3.5.5", "@types/slate": "0.47.1", "@types/slate-react": "0.22.5", "classnames": "2.2.6", diff --git a/packages/grafana-ui/rollup.config.ts b/packages/grafana-ui/rollup.config.ts index c79a2084fdd..105aeb30604 100644 --- a/packages/grafana-ui/rollup.config.ts +++ b/packages/grafana-ui/rollup.config.ts @@ -57,6 +57,7 @@ const buildCjsPackage = ({ env }) => { ], 'node_modules/immutable/dist/immutable.js': ['Record', 'Set', 'Map', 'List', 'OrderedSet', 'is', 'Stack'], '../../node_modules/esrever/esrever.js': ['reverse'], + '../../node_modules/bizcharts/es6/index.js': ['Chart', 'Geom', 'View', 'Tooltip', 'Legend'], }, }), resolve(), diff --git a/packages/grafana-ui/src/components/BigValue/BigValue.story.tsx b/packages/grafana-ui/src/components/BigValue/BigValue.story.tsx index 8d206c9d73e..6fba62e6017 100644 --- a/packages/grafana-ui/src/components/BigValue/BigValue.story.tsx +++ b/packages/grafana-ui/src/components/BigValue/BigValue.story.tsx @@ -1,14 +1,13 @@ import { storiesOf } from '@storybook/react'; -import { number, text } from '@storybook/addon-knobs'; -import { BigValue } from './BigValue'; +import { text } from '@storybook/addon-knobs'; +import { BigValue, SingleStatDisplayMode } from './BigValue'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { renderComponentWithTheme } from '../../utils/storybook/withTheme'; const getKnobs = () => { return { - value: text('value', 'Hello'), - valueFontSize: number('valueFontSize', 120), - prefix: text('prefix', ''), + value: text('value', '$5022'), + title: text('title', 'Total Earnings'), }; }; @@ -16,22 +15,37 @@ const BigValueStories = storiesOf('UI/BigValue', module); BigValueStories.addDecorator(withCenteredStory); -BigValueStories.add('Singlestat viz', () => { - const { value, prefix, valueFontSize } = getKnobs(); +interface StoryOptions { + mode: SingleStatDisplayMode; + width?: number; + height?: number; + noSparkline?: boolean; +} - return renderComponentWithTheme(BigValue, { - width: 300, - height: 250, - value: { - text: value, - numeric: NaN, - fontSize: valueFontSize + '%', - }, - prefix: prefix - ? { - text: prefix, - numeric: NaN, - } - : null, +function addStoryForMode(options: StoryOptions) { + BigValueStories.add(`Mode: ${SingleStatDisplayMode[options.mode]}`, () => { + const { value, title } = getKnobs(); + + return renderComponentWithTheme(BigValue, { + width: options.width || 400, + height: options.height || 300, + displayMode: options.mode, + value: { + text: value, + numeric: 5022, + color: 'red', + title, + }, + sparkline: { + minX: 0, + maxX: 5, + data: [[0, 10], [1, 20], [2, 15], [3, 25], [4, 5], [5, 10]], + }, + }); }); -}); +} + +addStoryForMode({ mode: SingleStatDisplayMode.Classic }); +addStoryForMode({ mode: SingleStatDisplayMode.Classic2 }); +addStoryForMode({ mode: SingleStatDisplayMode.Vibrant }); +addStoryForMode({ mode: SingleStatDisplayMode.Vibrant2 }); diff --git a/packages/grafana-ui/src/components/BigValue/BigValue.test.tsx b/packages/grafana-ui/src/components/BigValue/BigValue.test.tsx index 9c22b42e61d..390e3905aaa 100644 --- a/packages/grafana-ui/src/components/BigValue/BigValue.test.tsx +++ b/packages/grafana-ui/src/components/BigValue/BigValue.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { BigValue, Props } from './BigValue'; +import { BigValue, Props, SingleStatDisplayMode } from './BigValue'; import { getTheme } from '../../themes/index'; jest.mock('jquery', () => ({ @@ -11,6 +11,7 @@ const setup = (propOverrides?: object) => { const props: Props = { height: 300, width: 300, + displayMode: SingleStatDisplayMode.Classic, value: { text: '25', numeric: 25, @@ -29,7 +30,7 @@ const setup = (propOverrides?: object) => { }; }; -describe('Render BarGauge with basic options', () => { +describe('Render SingleStat with basic options', () => { it('should render', () => { const { wrapper } = setup(); expect(wrapper).toBeDefined(); diff --git a/packages/grafana-ui/src/components/BigValue/BigValue.tsx b/packages/grafana-ui/src/components/BigValue/BigValue.tsx index 3ec95605ebe..c4d67a06eb2 100644 --- a/packages/grafana-ui/src/components/BigValue/BigValue.tsx +++ b/packages/grafana-ui/src/components/BigValue/BigValue.tsx @@ -1,168 +1,413 @@ // Library -import React, { PureComponent, ReactNode, CSSProperties } from 'react'; -import $ from 'jquery'; -import { css, cx } from 'emotion'; +import React, { PureComponent, CSSProperties } from 'react'; +import tinycolor from 'tinycolor2'; +import { Chart, Geom } from 'bizcharts'; import { DisplayValue } from '@grafana/data'; // Utils import { getColorFromHexRgbOrName } from '../../utils'; // Types -import { Themeable } from '../../types'; -import { stylesFactory } from '../../themes/stylesFactory'; +import { Themeable, GrafanaTheme } from '../../types'; export interface BigValueSparkline { - data: any[][]; // [[number,number]] + data: any[][]; minX: number; maxX: number; - full: boolean; // full height - fillColor: string; - lineColor: string; +} + +export enum SingleStatDisplayMode { + Classic, + Classic2, + Vibrant, + Vibrant2, } export interface Props extends Themeable { height: number; width: number; value: DisplayValue; - prefix?: DisplayValue; - suffix?: DisplayValue; sparkline?: BigValueSparkline; - backgroundColor?: string; onClick?: React.MouseEventHandler; className?: string; + displayMode: SingleStatDisplayMode; } -const getStyles = stylesFactory(() => { - return { - wrapper: css` - position: 'relative'; - display: 'table'; - `, - title: css` - line-height: 1; - text-align: 'center'; - z-index: 1; - display: 'block'; - width: '100%'; - position: 'absolute'; - `, - value: css` - line-height: 1; - text-align: 'center'; - z-index: 1; - display: 'table-cell'; - vertical-align: 'middle'; - position: 'relative'; - font-size: '3em'; - font-weight: 500; - `, - }; -}); - -/* - * This visualization is still in POC state, needed more tests & better structure - */ export class BigValue extends PureComponent { - canvasElement: any; - - componentDidMount() { - this.draw(); - } - - componentDidUpdate() { - this.draw(); - } - - draw() { - const { sparkline, theme } = this.props; - - if (sparkline && this.canvasElement) { - const { data, minX, maxX, fillColor, lineColor } = sparkline; - - const options = { - legend: { show: false }, - series: { - lines: { - show: true, - fill: 1, - zero: false, - lineWidth: 1, - fillColor: getColorFromHexRgbOrName(fillColor, theme.type), - }, - }, - yaxes: { show: false }, - xaxis: { - show: false, - min: minX, - max: maxX, - }, - grid: { hoverable: false, show: false }, - }; - - const plotSeries = { - data, - color: getColorFromHexRgbOrName(lineColor, theme.type), - }; - - try { - $.plot(this.canvasElement, [plotSeries], options); - } catch (err) { - console.log('sparkline rendering error', err, options); - } - } - } - - renderText = (value?: DisplayValue, padding?: string): ReactNode => { - if (!value || !value.text) { - return null; - } - const css: CSSProperties = {}; - if (padding) { - css.padding = padding; - } - if (value.color) { - css.color = value.color; - } - if (value.fontSize) { - css.fontSize = value.fontSize; - } - - return {value.text}; - }; - - renderSparkline(sparkline: BigValueSparkline) { - const { height, width } = this.props; - - const plotCss: CSSProperties = {}; - plotCss.position = 'absolute'; - plotCss.bottom = '0px'; - plotCss.left = '0px'; - plotCss.width = width + 'px'; - - if (sparkline.full) { - const dynamicHeightMargin = height <= 100 ? 5 : Math.round(height / 100) * 15 + 5; - plotCss.height = height - dynamicHeightMargin + 'px'; - } else { - plotCss.height = Math.floor(height * 0.25) + 'px'; - } - return
(this.canvasElement = element)} />; - } - render() { - const { height, width, value, prefix, suffix, sparkline, backgroundColor, onClick, className } = this.props; - const styles = getStyles(); + const { value, onClick, className, sparkline } = this.props; + + const layout = calculateLayout(this.props); + const panelStyles = getPanelStyles(layout); + const valueAndTitleContainerStyles = getValueAndTitleContainerStyles(layout); + const valueStyles = getValueStyles(layout); + const titleStyles = getTitleStyles(layout); + return ( -
- {value.title &&
{value.title}
} - - - {this.renderText(prefix, '0px 2px 0px 0px')} - {this.renderText(value)} - {this.renderText(suffix)} - - - {sparkline && this.renderSparkline(sparkline)} +
+
+ {value.title &&
{value.title}
} +
{value.text}
+
+ {renderGraph(layout, sparkline)}
); } } + +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; + +interface LayoutResult { + titleFontSize: number; + valueFontSize: number; + chartHeight: number; + chartWidth: number; + type: LayoutType; + width: number; + height: number; + displayMode: SingleStatDisplayMode; + 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 SingleStatDisplayMode.Vibrant2: + case SingleStatDisplayMode.Classic: + case SingleStatDisplayMode.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 1px 1px 5px', + 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 1px 1px 5px', + lineHeight: LINE_HEIGHT, + }; + + switch (layout.displayMode) { + case SingleStatDisplayMode.Classic: + case SingleStatDisplayMode.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 SingleStatDisplayMode.Vibrant: + case SingleStatDisplayMode.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 SingleStatDisplayMode.Classic: + case SingleStatDisplayMode.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 SingleStatDisplayMode.Vibrant2: + geomRender = renderVibrant2Geom; + break; + case SingleStatDisplayMode.Classic: + geomRender = renderClassicAreaGeom; + break; + case SingleStatDisplayMode.Classic2: + geomRender = renderAreaGeom; + break; + } + + return ( + + {geomRender(layout)} + + ); +} + +function renderLineGeom(layout: LayoutResult) { + const lineStyle: any = { + stroke: '#CCC', + lineWidth: 2, + shadowBlur: 15, + shadowColor: '#444', + shadowOffsetY: 7, + }; + + return ; +} + +function renderVibrant2Geom(layout: LayoutResult) { + const lineStyle: any = { + stroke: '#CCC', + lineWidth: 2, + shadowBlur: 15, + shadowColor: '#444', + shadowOffsetY: -5, + }; + + return ( + <> + + + + ); +} + +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 ( + <> + + + + ); +} + +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 ; +} diff --git a/packages/grafana-ui/src/components/ThresholdsEditor/__snapshots__/ThresholdsEditor.test.tsx.snap b/packages/grafana-ui/src/components/ThresholdsEditor/__snapshots__/ThresholdsEditor.test.tsx.snap index 1ed5069a9bc..0e74e007f18 100644 --- a/packages/grafana-ui/src/components/ThresholdsEditor/__snapshots__/ThresholdsEditor.test.tsx.snap +++ b/packages/grafana-ui/src/components/ThresholdsEditor/__snapshots__/ThresholdsEditor.test.tsx.snap @@ -164,6 +164,8 @@ exports[`Render should render with base threshold 1`] = ` "md": "32px", "sm": "24px", }, + "isDark": true, + "isLight": false, "name": "Grafana Dark", "panelHeaderHeight": 28, "panelPadding": 8, @@ -331,6 +333,8 @@ exports[`Render should render with base threshold 1`] = ` "md": "32px", "sm": "24px", }, + "isDark": true, + "isLight": false, "name": "Grafana Dark", "panelHeaderHeight": 28, "panelPadding": 8, diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index c6a4e7b9d4f..4540d406628 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -46,7 +46,7 @@ export { Table } from './Table/Table'; export { TableInputCSV } from './Table/TableInputCSV'; // Visualizations -export { BigValue } from './BigValue/BigValue'; +export { BigValue, SingleStatDisplayMode } from './BigValue/BigValue'; export { Gauge } from './Gauge/Gauge'; export { Graph } from './Graph/Graph'; export { GraphLegend } from './Graph/GraphLegend'; diff --git a/packages/grafana-ui/src/themes/dark.ts b/packages/grafana-ui/src/themes/dark.ts index c78c7556c4e..9d9ae80533b 100644 --- a/packages/grafana-ui/src/themes/dark.ts +++ b/packages/grafana-ui/src/themes/dark.ts @@ -42,6 +42,8 @@ const basicColors = { const darkTheme: GrafanaTheme = { ...defaultTheme, type: GrafanaThemeType.Dark, + isDark: true, + isLight: false, name: 'Grafana Dark', colors: { ...basicColors, diff --git a/packages/grafana-ui/src/themes/light.ts b/packages/grafana-ui/src/themes/light.ts index 595a36f342a..8eeba2d1df5 100644 --- a/packages/grafana-ui/src/themes/light.ts +++ b/packages/grafana-ui/src/themes/light.ts @@ -42,6 +42,8 @@ const basicColors = { const lightTheme: GrafanaTheme = { ...defaultTheme, type: GrafanaThemeType.Light, + isDark: false, + isLight: true, name: 'Grafana Light', colors: { ...basicColors, diff --git a/packages/grafana-ui/src/types/theme.ts b/packages/grafana-ui/src/types/theme.ts index 6d66bdce3b0..23377b47443 100644 --- a/packages/grafana-ui/src/types/theme.ts +++ b/packages/grafana-ui/src/types/theme.ts @@ -92,6 +92,8 @@ export interface GrafanaThemeCommons { export interface GrafanaTheme extends GrafanaThemeCommons { type: GrafanaThemeType; + isDark: boolean; + isLight: boolean; background: { dropdown: string; scrollbar: string; diff --git a/public/app/features/dashboard/state/PanelQueryRunner.ts b/public/app/features/dashboard/state/PanelQueryRunner.ts index 85e9c7eca2d..e6712e17997 100644 --- a/public/app/features/dashboard/state/PanelQueryRunner.ts +++ b/public/app/features/dashboard/state/PanelQueryRunner.ts @@ -142,6 +142,7 @@ export class PanelQueryRunner { request.interval = norm.interval; request.intervalMs = norm.intervalMs; + console.log('to', request.range.to.valueOf()); this.pipeToSubject(runRequest(ds, request)); } catch (err) { diff --git a/public/app/plugins/panel/singlestat2/ColoringEditor.tsx b/public/app/plugins/panel/singlestat2/ColoringEditor.tsx deleted file mode 100644 index 24a6389aa5b..00000000000 --- a/public/app/plugins/panel/singlestat2/ColoringEditor.tsx +++ /dev/null @@ -1,63 +0,0 @@ -// Libraries -import React, { PureComponent } from 'react'; - -// Components -import { Switch, PanelOptionsGroup } from '@grafana/ui'; - -// Types -import { SingleStatOptions } from './types'; - -const labelWidth = 6; - -export interface Props { - options: SingleStatOptions; - onChange: (options: SingleStatOptions) => void; -} - -export class ColoringEditor extends PureComponent { - onToggleColorBackground = () => - this.props.onChange({ ...this.props.options, colorBackground: !this.props.options.colorBackground }); - - onToggleColorValue = () => this.props.onChange({ ...this.props.options, colorValue: !this.props.options.colorValue }); - - onToggleColorPrefix = () => - this.props.onChange({ ...this.props.options, colorPrefix: !this.props.options.colorPrefix }); - - onToggleColorPostfix = () => - this.props.onChange({ ...this.props.options, colorPostfix: !this.props.options.colorPostfix }); - - render() { - const { colorBackground, colorValue, colorPrefix, colorPostfix } = this.props.options; - - return ( - - - - - - - - - ); - } -} diff --git a/public/app/plugins/panel/singlestat2/FontSizeEditor.tsx b/public/app/plugins/panel/singlestat2/FontSizeEditor.tsx deleted file mode 100644 index ed82bbb4b43..00000000000 --- a/public/app/plugins/panel/singlestat2/FontSizeEditor.tsx +++ /dev/null @@ -1,70 +0,0 @@ -// Libraries -import React, { PureComponent } from 'react'; - -// Components -import { FormLabel, Select, PanelOptionsGroup } from '@grafana/ui'; - -// Types -import { SingleStatOptions } from './types'; -import { SelectableValue } from '@grafana/data'; - -const labelWidth = 6; - -export interface Props { - options: SingleStatOptions; - onChange: (options: SingleStatOptions) => void; -} - -const percents = ['20%', '30%', '50%', '70%', '80%', '100%', '110%', '120%', '150%', '170%', '200%']; -const fontSizeOptions = percents.map(v => { - return { value: v, label: v }; -}); - -export class FontSizeEditor extends PureComponent { - setPrefixFontSize = (v: SelectableValue) => - this.props.onChange({ ...this.props.options, prefixFontSize: v.value }); - - setValueFontSize = (v: SelectableValue) => - this.props.onChange({ ...this.props.options, valueFontSize: v.value }); - - setPostfixFontSize = (v: SelectableValue) => - this.props.onChange({ ...this.props.options, postfixFontSize: v.value }); - - render() { - const { prefixFontSize, valueFontSize, postfixFontSize } = this.props.options; - - return ( - -
- Prefix - option.value === valueFontSize)} - /> -
- -
- Postfix - item.value === options.displayMode)} + /> +
+
+ Color by +