mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Stat/Gauge: expose explicit font sizing (#29476)
This commit is contained in:
parent
7236a44a4f
commit
716117b7da
@ -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
|
||||
|
@ -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`;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user