diff --git a/packages/grafana-schema/src/common/common.gen.ts b/packages/grafana-schema/src/common/common.gen.ts index 6fca2bf56fd..ba472408431 100644 --- a/packages/grafana-schema/src/common/common.gen.ts +++ b/packages/grafana-schema/src/common/common.gen.ts @@ -552,6 +552,15 @@ export enum BigValueTextMode { ValueAndName = 'value_and_name', } +/** + * TODO docs + */ +export enum PercentChangeColorMode { + Inverted = 'inverted', + SameAsValue = 'same_as_value', + Standard = 'standard', +} + /** * TODO -- should not be table specific! * TODO docs diff --git a/packages/grafana-schema/src/common/mudball.cue b/packages/grafana-schema/src/common/mudball.cue index a8fe6f5c291..5ea26081f0f 100644 --- a/packages/grafana-schema/src/common/mudball.cue +++ b/packages/grafana-schema/src/common/mudball.cue @@ -190,6 +190,9 @@ BigValueJustifyMode: "auto" | "center" @cuetsy(kind="enum") // TODO docs BigValueTextMode: "auto" | "value" | "value_and_name" | "name" | "none" @cuetsy(kind="enum",memberNames="Auto|Value|ValueAndName|Name|None") +// TODO docs +PercentChangeColorMode: "standard" | "inverted" | "same_as_value" @cuetsy(kind="enum",memberNames="Standard|Inverted|SameAsValue") + // TODO -- should not be table specific! // TODO docs FieldTextAlignment: "auto" | "left" | "right" | "center" @cuetsy(kind="type") diff --git a/packages/grafana-schema/src/raw/composable/stat/panelcfg/x/StatPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/stat/panelcfg/x/StatPanelCfg_types.gen.ts index e2898e14142..597ad6cae8c 100644 --- a/packages/grafana-schema/src/raw/composable/stat/panelcfg/x/StatPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/stat/panelcfg/x/StatPanelCfg_types.gen.ts @@ -16,6 +16,7 @@ export interface Options extends common.SingleStatBaseOptions { colorMode: common.BigValueColorMode; graphMode: common.BigValueGraphMode; justifyMode: common.BigValueJustifyMode; + percentChangeColorMode: common.PercentChangeColorMode; showPercentChange: boolean; textMode: common.BigValueTextMode; wideLayout: boolean; @@ -25,6 +26,7 @@ export const defaultOptions: Partial = { colorMode: common.BigValueColorMode.Value, graphMode: common.BigValueGraphMode.Area, justifyMode: common.BigValueJustifyMode.Auto, + percentChangeColorMode: common.PercentChangeColorMode.Standard, showPercentChange: false, textMode: common.BigValueTextMode.Auto, wideLayout: true, diff --git a/packages/grafana-ui/src/components/BigValue/BigValue.tsx b/packages/grafana-ui/src/components/BigValue/BigValue.tsx index dea9b428f56..0b89924d885 100644 --- a/packages/grafana-ui/src/components/BigValue/BigValue.tsx +++ b/packages/grafana-ui/src/components/BigValue/BigValue.tsx @@ -2,7 +2,7 @@ import { cx } from '@emotion/css'; import React, { PureComponent } from 'react'; import { DisplayValue, DisplayValueAlignmentFactors, FieldSparkline } from '@grafana/data'; -import { VizTextDisplayOptions } from '@grafana/schema'; +import { PercentChangeColorMode, VizTextDisplayOptions } from '@grafana/schema'; import { Themeable2 } from '../../types'; import { clearButtonStyles } from '../Button'; @@ -67,6 +67,8 @@ export interface Props extends Themeable2 { textMode?: BigValueTextMode; /** If true disables the tooltip */ hasLinks?: boolean; + /** Percent change color mode */ + percentChangeColorMode?: PercentChangeColorMode; /** * If part of a series of stat panes, this is the total number. @@ -94,6 +96,7 @@ export class BigValue extends PureComponent { const titleStyles = layout.getTitleStyles(); const textValues = layout.textValues; const percentChange = this.props.value.percentChange; + const percentChangeColorMode = this.props.percentChangeColorMode; const showPercentChange = percentChange != null && !Number.isNaN(percentChange); // When there is an outer data link this tooltip will override the outer native tooltip @@ -106,7 +109,10 @@ export class BigValue extends PureComponent { {textValues.title &&
{textValues.title}
} {showPercentChange && ( - + )} {layout.renderChart()} diff --git a/packages/grafana-ui/src/components/BigValue/BigValueLayout.test.tsx b/packages/grafana-ui/src/components/BigValue/BigValueLayout.test.tsx index b48f246478d..9c2c9f6ef3f 100644 --- a/packages/grafana-ui/src/components/BigValue/BigValueLayout.test.tsx +++ b/packages/grafana-ui/src/components/BigValue/BigValueLayout.test.tsx @@ -1,7 +1,17 @@ +import { CSSProperties } from 'react'; + import { createTheme, FieldType } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { PercentChangeColorMode } from '@grafana/schema'; import { Props, BigValueColorMode, BigValueGraphMode, BigValueTextMode } from './BigValue'; -import { buildLayout, StackedWithChartLayout, StackedWithNoChartLayout, WideWithChartLayout } from './BigValueLayout'; +import { + buildLayout, + getPercentChangeColor, + StackedWithChartLayout, + StackedWithNoChartLayout, + WideWithChartLayout, +} from './BigValueLayout'; function getProps(propOverrides?: Partial): Props { const props: Props = { @@ -29,6 +39,10 @@ function getProps(propOverrides?: Partial): Props { return props; } +const valueStyles: CSSProperties = { + color: 'purple', +}; + describe('BigValueLayout', () => { describe('buildLayout', () => { it('should auto select to stacked layout', () => { @@ -88,4 +102,70 @@ describe('BigValueLayout', () => { expect(layout).toBeInstanceOf(WideWithChartLayout); }); }); + + describe('percentChangeColor', () => { + const themeVisualizationColors = config.theme2.visualization; + const red = themeVisualizationColors.getColorByName('red'); + const green = themeVisualizationColors.getColorByName('green'); + it('standard negative should be red', () => { + const percentChange = -10; + const color = getPercentChangeColor( + percentChange, + PercentChangeColorMode.Standard, + valueStyles, + themeVisualizationColors + ); + expect(color).toBe(red); + }); + it('standard positive should be green', () => { + const percentChange = 10; + const color = getPercentChangeColor( + percentChange, + PercentChangeColorMode.Standard, + valueStyles, + themeVisualizationColors + ); + expect(color).toBe(green); + }); + it('inverted negative should be green', () => { + const percentChange = -10; + const color = getPercentChangeColor( + percentChange, + PercentChangeColorMode.Inverted, + valueStyles, + themeVisualizationColors + ); + expect(color).toBe(green); + }); + it('inverted positive should be red', () => { + const percentChange = 10; + const color = getPercentChangeColor( + percentChange, + PercentChangeColorMode.Inverted, + valueStyles, + themeVisualizationColors + ); + expect(color).toBe(red); + }); + it('same as value negative should be purple', () => { + const percentChange = -10; + const color = getPercentChangeColor( + percentChange, + PercentChangeColorMode.SameAsValue, + valueStyles, + themeVisualizationColors + ); + expect(color).toBe('purple'); + }); + it('same as value positive should be purple', () => { + const percentChange = 10; + const color = getPercentChangeColor( + percentChange, + PercentChangeColorMode.SameAsValue, + valueStyles, + themeVisualizationColors + ); + expect(color).toBe('purple'); + }); + }); }); diff --git a/packages/grafana-ui/src/components/BigValue/BigValueLayout.tsx b/packages/grafana-ui/src/components/BigValue/BigValueLayout.tsx index db0d506f86e..5535847e978 100644 --- a/packages/grafana-ui/src/components/BigValue/BigValueLayout.tsx +++ b/packages/grafana-ui/src/components/BigValue/BigValueLayout.tsx @@ -1,8 +1,8 @@ import React, { CSSProperties } from 'react'; import tinycolor from 'tinycolor2'; -import { formattedValueToString, DisplayValue, FieldConfig, FieldType } from '@grafana/data'; -import { GraphDrawStyle, GraphFieldConfig } from '@grafana/schema'; +import { formattedValueToString, DisplayValue, FieldConfig, FieldType, ThemeVisualizationColors } from '@grafana/data'; +import { GraphDrawStyle, GraphFieldConfig, PercentChangeColorMode } from '@grafana/schema'; import { getTextColorForAlphaBackground } from '../../utils'; import { calculateFontSize } from '../../utils/measureText'; @@ -103,16 +103,17 @@ export abstract class BigValueLayout { return styles; } - getPercentChangeStyles(percentChange: number): PercentChangeStyles { + getPercentChangeStyles( + percentChange: number, + percentChangeColorMode: PercentChangeColorMode | undefined, + valueStyles: React.CSSProperties + ): PercentChangeStyles { const VALUE_TO_PERCENT_CHANGE_RATIO = 2.5; const valueContainerStyles = this.getValueAndTitleContainerStyles(); const percentFontSize = Math.max(this.valueFontSize / VALUE_TO_PERCENT_CHANGE_RATIO, 12); let iconSize = Math.max(this.valueFontSize / 3, 10); - - const color = - percentChange > 0 - ? this.props.theme.visualization.getColorByName('green') - : this.props.theme.visualization.getColorByName('red'); + const themeVisualizationColors = this.props.theme.visualization; + const color = getPercentChangeColor(percentChange, percentChangeColorMode, valueStyles, themeVisualizationColors); const containerStyles: CSSProperties = { fontSize: percentFontSize, @@ -597,3 +598,18 @@ export interface PercentChangeStyles { containerStyles: CSSProperties; iconSize: number; } + +export function getPercentChangeColor( + percentChange: number, + percentChangeColorMode: PercentChangeColorMode | undefined, + valueStyles: CSSProperties, + themeVisualizationColors: ThemeVisualizationColors +): string | undefined { + if (percentChangeColorMode === PercentChangeColorMode.SameAsValue) { + return valueStyles.color; + } else { + return percentChange * (percentChangeColorMode === PercentChangeColorMode.Inverted ? -1 : 1) > 0 + ? themeVisualizationColors.getColorByName('green') + : themeVisualizationColors.getColorByName('red'); + } +} diff --git a/public/app/plugins/panel/stat/StatPanel.tsx b/public/app/plugins/panel/stat/StatPanel.tsx index 4e1b45849b8..2092b8d2989 100644 --- a/public/app/plugins/panel/stat/StatPanel.tsx +++ b/public/app/plugins/panel/stat/StatPanel.tsx @@ -48,6 +48,7 @@ export class StatPanel extends PureComponent> { onClick={openMenu} className={targetClassName} disableWideLayout={!options.wideLayout} + percentChangeColorMode={options.percentChangeColorMode} /> ); }; diff --git a/public/app/plugins/panel/stat/module.tsx b/public/app/plugins/panel/stat/module.tsx index 5d0a5757c05..d5c297fc0fe 100644 --- a/public/app/plugins/panel/stat/module.tsx +++ b/public/app/plugins/panel/stat/module.tsx @@ -1,5 +1,11 @@ import { PanelPlugin } from '@grafana/data'; -import { BigValueColorMode, BigValueGraphMode, BigValueJustifyMode, BigValueTextMode } from '@grafana/schema'; +import { + BigValueColorMode, + BigValueGraphMode, + BigValueJustifyMode, + BigValueTextMode, + PercentChangeColorMode, +} from '@grafana/schema'; import { commonOptionsBuilder, sharedSingleStatMigrationHandler } from '@grafana/ui'; import { statPanelChangedHandler } from './StatMigrations'; @@ -94,6 +100,20 @@ export const plugin = new PanelPlugin(StatPanel) defaultValue: defaultOptions.showPercentChange, category: mainCategory, showIf: (config) => !config.reduceOptions.values, + }) + .addSelect({ + path: 'percentChangeColorMode', + name: 'Percent change color mode', + defaultValue: defaultOptions.percentChangeColorMode, + category: mainCategory, + settings: { + options: [ + { value: PercentChangeColorMode.Standard, label: 'Standard' }, + { value: PercentChangeColorMode.Inverted, label: 'Inverted' }, + { value: PercentChangeColorMode.SameAsValue, label: 'Same as Value' }, + ], + }, + showIf: (config) => config.showPercentChange, }); }) .setNoPadding() diff --git a/public/app/plugins/panel/stat/panelcfg.cue b/public/app/plugins/panel/stat/panelcfg.cue index c38de15d9b1..94ebfadc51e 100644 --- a/public/app/plugins/panel/stat/panelcfg.cue +++ b/public/app/plugins/panel/stat/panelcfg.cue @@ -27,12 +27,13 @@ composableKinds: PanelCfg: { schema: { Options: { common.SingleStatBaseOptions - graphMode: common.BigValueGraphMode & (*"area" | _) - colorMode: common.BigValueColorMode & (*"value" | _) - justifyMode: common.BigValueJustifyMode & (*"auto" | _) - textMode: common.BigValueTextMode & (*"auto" | _) - wideLayout: bool | *true - showPercentChange: bool | *false + graphMode: common.BigValueGraphMode & (*"area" | _) + colorMode: common.BigValueColorMode & (*"value" | _) + justifyMode: common.BigValueJustifyMode & (*"auto" | _) + textMode: common.BigValueTextMode & (*"auto" | _) + wideLayout: bool | *true + showPercentChange: bool | *false + percentChangeColorMode: common.PercentChangeColorMode & (*"standard" | _) } @cuetsy(kind="interface") } }] diff --git a/public/app/plugins/panel/stat/panelcfg.gen.ts b/public/app/plugins/panel/stat/panelcfg.gen.ts index 2f02876b845..27e1dc956d0 100644 --- a/public/app/plugins/panel/stat/panelcfg.gen.ts +++ b/public/app/plugins/panel/stat/panelcfg.gen.ts @@ -14,6 +14,7 @@ export interface Options extends common.SingleStatBaseOptions { colorMode: common.BigValueColorMode; graphMode: common.BigValueGraphMode; justifyMode: common.BigValueJustifyMode; + percentChangeColorMode: common.PercentChangeColorMode; showPercentChange: boolean; textMode: common.BigValueTextMode; wideLayout: boolean; @@ -23,6 +24,7 @@ export const defaultOptions: Partial = { colorMode: common.BigValueColorMode.Value, graphMode: common.BigValueGraphMode.Area, justifyMode: common.BigValueJustifyMode.Auto, + percentChangeColorMode: common.PercentChangeColorMode.Standard, showPercentChange: false, textMode: common.BigValueTextMode.Auto, wideLayout: true,