Commit 716117b7 by Ryan McKinley Committed by GitHub

Stat/Gauge: expose explicit font sizing (#29476)

parent 7236a44a
......@@ -19,6 +19,17 @@ export interface DisplayValue extends FormattedValue {
}
/**
* 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
*/
......
......@@ -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<Props> {
}
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<Props> {
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`;
......
// 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;
......
......@@ -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;
this.valueFontSize = calculateFontSize(
this.valueToAlignTo,
this.maxTextWidth,
this.maxTextHeight - this.chartHeight - titleHeight,
LINE_HEIGHT
);
if (this.valueToAlignTo.length) {
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);
......
......@@ -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<HTMLElement>;
className?: string;
}
......@@ -108,7 +110,7 @@ export class Gauge extends PureComponent<Props> {
// 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<Props> {
}
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<Props> {
<div
style={{
textAlign: 'center',
fontSize: autoProps.titleFontSize,
fontSize: text?.titleSize ?? autoProps.titleFontSize,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
......
......@@ -16,11 +16,13 @@ import {
ThresholdsConfig,
validateFieldConfig,
FieldColorModeId,
TextDisplayOptions,
} from '@grafana/data';
export interface SingleStatBaseOptions {
reduceOptions: ReduceDataOptions;
orientation: VizOrientation;
text?: TextDisplayOptions;
}
const optionsToKeep = ['reduceOptions', 'orientation'];
......
......@@ -38,6 +38,7 @@ export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> {
height={height}
orientation={orientation}
field={field}
text={options.text}
display={processor}
theme={config.theme}
itemSpacing={this.getItemSpacing()}
......
......@@ -23,6 +23,7 @@ export class GaugePanel extends PureComponent<PanelProps<GaugeOptions>> {
width={width}
height={height}
field={field}
text={options.text}
showThresholdLabels={options.showThresholdLabels}
showThresholdMarkers={options.showThresholdMarkers}
theme={config.theme}
......
......@@ -56,6 +56,7 @@ export class StatPanel extends PureComponent<PanelProps<StatPanelOptions>> {
justifyMode={options.justifyMode}
textMode={this.getTextMode()}
alignmentFactors={alignmentFactors}
text={options.text}
width={width}
height={height}
theme={config.theme}
......
......@@ -25,7 +25,8 @@ export interface StatPanelOptions extends SingleStatBaseOptions {
export function addStandardDataReduceOptions(
builder: PanelOptionsEditorBuilder<SingleStatBaseOptions>,
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,
});
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment