From 716117b7da091562b21cf6c8a903d1af2dbf35fa Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Fri, 4 Dec 2020 10:03:59 -0800 Subject: [PATCH] Stat/Gauge: expose explicit font sizing (#29476) --- .../grafana-data/src/types/displayValue.ts | 11 +++ .../src/components/BarGauge/BarGauge.tsx | 57 ++++++++----- .../src/components/BigValue/BigValue.tsx | 4 +- .../components/BigValue/BigValueLayout.tsx | 79 ++++++++++++------- .../grafana-ui/src/components/Gauge/Gauge.tsx | 8 +- .../SingleStatShared/SingleStatBaseOptions.ts | 2 + .../plugins/panel/bargauge/BarGaugePanel.tsx | 1 + public/app/plugins/panel/gauge/GaugePanel.tsx | 1 + public/app/plugins/panel/stat/StatPanel.tsx | 1 + public/app/plugins/panel/stat/types.ts | 31 +++++++- 10 files changed, 141 insertions(+), 54 deletions(-) diff --git a/packages/grafana-data/src/types/displayValue.ts b/packages/grafana-data/src/types/displayValue.ts index b8542c881cb..4d0a42d9c4f 100644 --- a/packages/grafana-data/src/types/displayValue.ts +++ b/packages/grafana-data/src/types/displayValue.ts @@ -18,6 +18,17 @@ export interface DisplayValue extends FormattedValue { title?: string; } +/** + * Explicit control for text settings + */ +export interface TextDisplayOptions { + /* Explicit text size */ + titleSize?: number; + + /* Explicit text size */ + valueSize?: number; +} + /** * These represents the display value with the longest title and text. * Used to align widths and heights when displaying multiple DisplayValues diff --git a/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx b/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx index f051c6e862d..d67f110b53d 100644 --- a/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx +++ b/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx @@ -14,6 +14,7 @@ import { getFieldColorMode, getColorForTheme, FALLBACK_COLOR, + TextDisplayOptions, } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; @@ -42,6 +43,7 @@ export interface Props extends Themeable { display?: DisplayProcessor; value: DisplayValue; orientation: VizOrientation; + text?: TextDisplayOptions; itemSpacing?: number; lcdCellWidth?: number; displayMode: BarGaugeDisplayMode; @@ -172,7 +174,7 @@ export class BarGauge extends PureComponent { } renderRetroBars(): ReactNode { - const { field, value, itemSpacing, alignmentFactors, orientation, lcdCellWidth } = this.props; + const { field, value, itemSpacing, alignmentFactors, orientation, lcdCellWidth, text } = this.props; const { valueHeight, valueWidth, @@ -193,7 +195,7 @@ export class BarGauge extends PureComponent { const valueColor = getValueColor(this.props); const valueToBaseSizeOn = alignmentFactors ? alignmentFactors : value; - const valueStyles = getValueStyles(valueToBaseSizeOn, valueColor, valueWidth, valueHeight, orientation); + const valueStyles = getValueStyles(valueToBaseSizeOn, valueColor, valueWidth, valueHeight, orientation, text); const containerStyles: CSSProperties = { width: `${wrapperWidth}px`, @@ -270,7 +272,7 @@ function isVertical(orientation: VizOrientation) { } function calculateTitleDimensions(props: Props): TitleDimensions { - const { height, width, alignmentFactors, orientation } = props; + const { height, width, alignmentFactors, orientation, text } = props; const title = alignmentFactors ? alignmentFactors.title : props.value.title; if (!title) { @@ -278,16 +280,26 @@ function calculateTitleDimensions(props: Props): TitleDimensions { } if (isVertical(orientation)) { + const fontSize = text?.titleSize ?? 14; return { - fontSize: 14, + fontSize: fontSize, width: width, - height: 14 * TITLE_LINE_HEIGHT, + height: fontSize * TITLE_LINE_HEIGHT, placement: 'below', }; } // if height above 40 put text to above bar if (height > 40) { + if (text?.titleSize) { + return { + fontSize: text?.titleSize, + width: 0, + height: text.titleSize * TITLE_LINE_HEIGHT, + placement: 'above', + }; + } + const maxTitleHeightRatio = 0.45; const titleHeight = Math.max(Math.min(height * maxTitleHeightRatio, MAX_VALUE_HEIGHT), 17); @@ -306,7 +318,7 @@ function calculateTitleDimensions(props: Props): TitleDimensions { const textSize = measureText(title, titleFontSize); return { - fontSize: titleFontSize, + fontSize: text?.titleSize ?? titleFontSize, height: 0, width: textSize.width + 15, placement: 'left', @@ -370,7 +382,7 @@ interface BarAndValueDimensions { } function calculateBarAndValueDimensions(props: Props): BarAndValueDimensions { - const { height, width, orientation } = props; + const { height, width, orientation, text } = props; const titleDim = calculateTitleDimensions(props); let maxBarHeight = 0; @@ -381,14 +393,23 @@ function calculateBarAndValueDimensions(props: Props): BarAndValueDimensions { let wrapperHeight = 0; if (isVertical(orientation)) { - valueHeight = Math.min(Math.max(height * 0.1, MIN_VALUE_HEIGHT), MAX_VALUE_HEIGHT); + if (text?.valueSize) { + valueHeight = text.valueSize * VALUE_LINE_HEIGHT; + } else { + valueHeight = Math.min(Math.max(height * 0.1, MIN_VALUE_HEIGHT), MAX_VALUE_HEIGHT); + } valueWidth = width; maxBarHeight = height - (titleDim.height + valueHeight); maxBarWidth = width; wrapperWidth = width; wrapperHeight = height - titleDim.height; } else { - valueHeight = height - titleDim.height; + if (text?.valueSize) { + valueHeight = text.valueSize * VALUE_LINE_HEIGHT; + } else { + valueHeight = height - titleDim.height; + } + valueWidth = Math.max(Math.min(width * 0.2, MAX_VALUE_WIDTH), MIN_VALUE_WIDTH); maxBarHeight = height - titleDim.height; maxBarWidth = width - valueWidth - titleDim.width; @@ -420,14 +441,14 @@ export function getValuePercent(value: number, minValue: number, maxValue: numbe * Only exported to for unit test */ export function getBasicAndGradientStyles(props: Props): BasicAndGradientStyles { - const { displayMode, field, value, alignmentFactors, orientation, theme } = props; + const { displayMode, field, value, alignmentFactors, orientation, theme, text } = props; const { valueWidth, valueHeight, maxBarHeight, maxBarWidth } = calculateBarAndValueDimensions(props); const valuePercent = getValuePercent(value.numeric, field.min!, field.max!); const valueColor = getValueColor(props); const valueToBaseSizeOn = alignmentFactors ? alignmentFactors : value; - const valueStyles = getValueStyles(valueToBaseSizeOn, valueColor, valueWidth, valueHeight, orientation); + const valueStyles = getValueStyles(valueToBaseSizeOn, valueColor, valueWidth, valueHeight, orientation, text); const isBasic = displayMode === 'basic'; const wrapperStyles: CSSProperties = { @@ -581,7 +602,8 @@ function getValueStyles( color: string, width: number, height: number, - orientation: VizOrientation + orientation: VizOrientation, + text?: TextDisplayOptions ): CSSProperties { const styles: CSSProperties = { color, @@ -597,15 +619,12 @@ function getValueStyles( const formattedValueString = formattedValueToString(value); if (isVertical(orientation)) { - styles.fontSize = calculateFontSize(formattedValueString, textWidth, height, VALUE_LINE_HEIGHT); + styles.fontSize = text?.valueSize ?? calculateFontSize(formattedValueString, textWidth, height, VALUE_LINE_HEIGHT); styles.justifyContent = `center`; } else { - styles.fontSize = calculateFontSize( - formattedValueString, - textWidth - VALUE_LEFT_PADDING * 2, - height, - VALUE_LINE_HEIGHT - ); + styles.fontSize = + text?.valueSize ?? + calculateFontSize(formattedValueString, textWidth - VALUE_LEFT_PADDING * 2, height, VALUE_LINE_HEIGHT); styles.justifyContent = `flex-end`; styles.paddingLeft = `${VALUE_LEFT_PADDING}px`; styles.paddingRight = `${VALUE_LEFT_PADDING}px`; diff --git a/packages/grafana-ui/src/components/BigValue/BigValue.tsx b/packages/grafana-ui/src/components/BigValue/BigValue.tsx index 41fdc774a3b..ade30ba069c 100644 --- a/packages/grafana-ui/src/components/BigValue/BigValue.tsx +++ b/packages/grafana-ui/src/components/BigValue/BigValue.tsx @@ -1,6 +1,6 @@ // Library import React, { PureComponent } from 'react'; -import { DisplayValue, GraphSeriesValue, DisplayValueAlignmentFactors } from '@grafana/data'; +import { DisplayValue, GraphSeriesValue, DisplayValueAlignmentFactors, TextDisplayOptions } from '@grafana/data'; // Types import { Themeable } from '../../types'; @@ -64,6 +64,8 @@ export interface Props extends Themeable { justifyMode?: BigValueJustifyMode; /** Factors that should influence the positioning of the text */ alignmentFactors?: DisplayValueAlignmentFactors; + /** Explicit font size control */ + text?: TextDisplayOptions; /** Specify which text should be visible in the BigValue */ textMode?: BigValueTextMode; diff --git a/packages/grafana-ui/src/components/BigValue/BigValueLayout.tsx b/packages/grafana-ui/src/components/BigValue/BigValueLayout.tsx index 8ec540bee55..7e1e7bbbf42 100644 --- a/packages/grafana-ui/src/components/BigValue/BigValueLayout.tsx +++ b/packages/grafana-ui/src/components/BigValue/BigValueLayout.tsx @@ -29,7 +29,7 @@ export abstract class BigValueLayout { textValues: BigValueTextValues; constructor(private props: Props) { - const { width, height, value, theme } = props; + const { width, height, value, theme, text } = props; this.valueColor = getColorForTheme(value.color || 'green', theme); this.panelPadding = height > 100 ? 12 : 8; @@ -43,6 +43,18 @@ export abstract class BigValueLayout { this.chartWidth = 0; this.maxTextWidth = width - this.panelPadding * 2; this.maxTextHeight = height - this.panelPadding * 2; + + // Explicit font sizing + if (text) { + if (text.titleSize) { + this.titleFontSize = text.titleSize; + this.titleToAlignTo = undefined; + } + if (text.valueSize) { + this.valueFontSize = text.valueSize; + this.valueToAlignTo = ''; + } + } } getTitleStyles(): CSSProperties { @@ -235,9 +247,9 @@ export class WideNoChartLayout extends BigValueLayout { constructor(props: Props) { super(props); - const valueWidthPercent = 0.3; + const valueWidthPercent = this.titleToAlignTo?.length ? 0.3 : 1.0; - if (this.titleToAlignTo && this.titleToAlignTo.length > 0) { + if (this.valueToAlignTo.length) { // initial value size this.valueFontSize = calculateFontSize( this.valueToAlignTo, @@ -245,7 +257,9 @@ export class WideNoChartLayout extends BigValueLayout { this.maxTextHeight, LINE_HEIGHT ); + } + if (this.titleToAlignTo?.length) { // How big can we make the title and still have it fit this.titleFontSize = calculateFontSize( this.titleToAlignTo, @@ -257,9 +271,6 @@ export class WideNoChartLayout extends BigValueLayout { // make sure it's a bit smaller than valueFontSize this.titleFontSize = Math.min(this.valueFontSize * 0.7, this.titleFontSize); - } else { - // if no title wide - this.valueFontSize = calculateFontSize(this.valueToAlignTo, this.maxTextWidth, this.maxTextHeight, LINE_HEIGHT); } } @@ -292,6 +303,7 @@ export class WideWithChartLayout extends BigValueLayout { super(props); const { width, height } = props; + const chartHeightPercent = 0.5; const titleWidthPercent = 0.6; const valueWidthPercent = 1 - titleWidthPercent; @@ -300,7 +312,7 @@ export class WideWithChartLayout extends BigValueLayout { this.chartWidth = width; this.chartHeight = height * chartHeightPercent; - if (this.titleToAlignTo && this.titleToAlignTo.length > 0) { + if (this.titleToAlignTo?.length) { this.titleFontSize = calculateFontSize( this.titleToAlignTo, this.maxTextWidth * titleWidthPercent, @@ -310,12 +322,14 @@ export class WideWithChartLayout extends BigValueLayout { ); } - this.valueFontSize = calculateFontSize( - this.valueToAlignTo, - this.maxTextWidth * valueWidthPercent, - this.maxTextHeight * chartHeightPercent, - LINE_HEIGHT - ); + if (this.valueToAlignTo.length) { + this.valueFontSize = calculateFontSize( + this.valueToAlignTo, + this.maxTextWidth * valueWidthPercent, + this.maxTextHeight * chartHeightPercent, + LINE_HEIGHT + ); + } } getValueAndTitleContainerStyles() { @@ -350,7 +364,7 @@ export class StackedWithChartLayout extends BigValueLayout { this.chartHeight = height * chartHeightPercent; this.chartWidth = width; - if (this.titleToAlignTo && this.titleToAlignTo.length > 0) { + if (this.titleToAlignTo?.length) { this.titleFontSize = calculateFontSize( this.titleToAlignTo, this.maxTextWidth, @@ -358,19 +372,22 @@ export class StackedWithChartLayout extends BigValueLayout { LINE_HEIGHT, MAX_TITLE_SIZE ); + } + titleHeight = this.titleFontSize * LINE_HEIGHT; - titleHeight = this.titleFontSize * LINE_HEIGHT; + if (this.valueToAlignTo.length) { + this.valueFontSize = calculateFontSize( + this.valueToAlignTo, + this.maxTextWidth, + this.maxTextHeight - this.chartHeight - titleHeight, + LINE_HEIGHT + ); } - this.valueFontSize = calculateFontSize( - this.valueToAlignTo, - this.maxTextWidth, - this.maxTextHeight - this.chartHeight - titleHeight, - LINE_HEIGHT - ); - // make title fontsize it's a bit smaller than valueFontSize - this.titleFontSize = Math.min(this.valueFontSize * 0.7, this.titleFontSize); + if (this.titleToAlignTo?.length) { + this.titleFontSize = Math.min(this.valueFontSize * 0.7, this.titleFontSize); + } // make chart take up unused space this.chartHeight = height - this.titleFontSize * LINE_HEIGHT - this.valueFontSize * LINE_HEIGHT; @@ -398,7 +415,7 @@ export class StackedWithNoChartLayout extends BigValueLayout { const titleHeightPercent = 0.15; let titleHeight = 0; - if (this.titleToAlignTo && this.titleToAlignTo.length > 0) { + if (this.titleToAlignTo?.length) { this.titleFontSize = calculateFontSize( this.titleToAlignTo, this.maxTextWidth, @@ -410,12 +427,14 @@ export class StackedWithNoChartLayout extends BigValueLayout { titleHeight = this.titleFontSize * LINE_HEIGHT; } - this.valueFontSize = calculateFontSize( - this.valueToAlignTo, - this.maxTextWidth, - this.maxTextHeight - titleHeight, - LINE_HEIGHT - ); + if (this.valueToAlignTo.length) { + this.valueFontSize = calculateFontSize( + this.valueToAlignTo, + this.maxTextWidth, + this.maxTextHeight - titleHeight, + LINE_HEIGHT + ); + } // make title fontsize it's a bit smaller than valueFontSize this.titleFontSize = Math.min(this.valueFontSize * 0.7, this.titleFontSize); diff --git a/packages/grafana-ui/src/components/Gauge/Gauge.tsx b/packages/grafana-ui/src/components/Gauge/Gauge.tsx index 2e98f8f7cd0..0372c5d9ae5 100644 --- a/packages/grafana-ui/src/components/Gauge/Gauge.tsx +++ b/packages/grafana-ui/src/components/Gauge/Gauge.tsx @@ -10,6 +10,7 @@ import { getColorForTheme, FieldColorModeId, FALLBACK_COLOR, + TextDisplayOptions, } from '@grafana/data'; import { Themeable } from '../../types'; import { calculateFontSize } from '../../utils/measureText'; @@ -21,6 +22,7 @@ export interface Props extends Themeable { showThresholdLabels: boolean; width: number; value: DisplayValue; + text?: TextDisplayOptions; onClick?: React.MouseEventHandler; className?: string; } @@ -108,7 +110,7 @@ export class Gauge extends PureComponent { // remove gauge & marker width (on left and right side) // and 10px is some padding that flot adds to the outer canvas const valueWidth = valueWidthBase - ((gaugeWidth + (showThresholdMarkers ? thresholdMarkersWidth : 0)) * 2 + 10); - const fontSize = calculateFontSize(text, valueWidth, dimension, 1, gaugeWidth * 1.7); + const fontSize = this.props.text?.valueSize ?? calculateFontSize(text, valueWidth, dimension, 1, gaugeWidth * 1.7); const thresholdLabelFontSize = fontSize / 2.5; let min = field.min!; @@ -180,7 +182,7 @@ export class Gauge extends PureComponent { } renderVisualization = () => { - const { width, value, height, onClick } = this.props; + const { width, value, height, onClick, text } = this.props; const autoProps = calculateGaugeAutoProps(width, height, value.title); return ( @@ -194,7 +196,7 @@ export class Gauge extends PureComponent {
> { height={height} orientation={orientation} field={field} + text={options.text} display={processor} theme={config.theme} itemSpacing={this.getItemSpacing()} diff --git a/public/app/plugins/panel/gauge/GaugePanel.tsx b/public/app/plugins/panel/gauge/GaugePanel.tsx index 47f3f07c1ef..9b164d31172 100644 --- a/public/app/plugins/panel/gauge/GaugePanel.tsx +++ b/public/app/plugins/panel/gauge/GaugePanel.tsx @@ -23,6 +23,7 @@ export class GaugePanel extends PureComponent> { width={width} height={height} field={field} + text={options.text} showThresholdLabels={options.showThresholdLabels} showThresholdMarkers={options.showThresholdMarkers} theme={config.theme} diff --git a/public/app/plugins/panel/stat/StatPanel.tsx b/public/app/plugins/panel/stat/StatPanel.tsx index e9d5049498c..cbf51f068d0 100644 --- a/public/app/plugins/panel/stat/StatPanel.tsx +++ b/public/app/plugins/panel/stat/StatPanel.tsx @@ -56,6 +56,7 @@ export class StatPanel extends PureComponent> { justifyMode={options.justifyMode} textMode={this.getTextMode()} alignmentFactors={alignmentFactors} + text={options.text} width={width} height={height} theme={config.theme} diff --git a/public/app/plugins/panel/stat/types.ts b/public/app/plugins/panel/stat/types.ts index 7e2f96e0115..42e11589cc8 100644 --- a/public/app/plugins/panel/stat/types.ts +++ b/public/app/plugins/panel/stat/types.ts @@ -25,7 +25,8 @@ export interface StatPanelOptions extends SingleStatBaseOptions { export function addStandardDataReduceOptions( builder: PanelOptionsEditorBuilder, includeOrientation = true, - includeFieldMatcher = true + includeFieldMatcher = true, + includeTextSizes = true ) { builder.addRadio({ path: 'reduceOptions.values', @@ -108,4 +109,32 @@ export function addStandardDataReduceOptions( defaultValue: 'auto', }); } + + if (includeTextSizes) { + builder.addNumberInput({ + path: 'text.titleSize', + category: ['Text size'], + name: 'Title', + settings: { + placeholder: 'Auto', + integer: false, + min: 1, + max: 200, + }, + defaultValue: undefined, + }); + + builder.addNumberInput({ + path: 'text.valueSize', + category: ['Text size'], + name: 'Value', + settings: { + placeholder: 'Auto', + integer: false, + min: 1, + max: 200, + }, + defaultValue: undefined, + }); + } }