@grafana/ui
Version:
Grafana Components Library
517 lines (514 loc) • 17.8 kB
JavaScript
import { jsxs, jsx } from 'react/jsx-runtime';
import { cx } from '@emotion/css';
import { PureComponent } from 'react';
import tinycolor from 'tinycolor2';
import { VizOrientation, ThresholdsMode, GAUGE_DEFAULT_MINIMUM, GAUGE_DEFAULT_MAXIMUM, formattedValueToString, FALLBACK_COLOR, getFieldColorMode, FieldColorModeId } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { BarGaugeDisplayMode, BarGaugeValueMode, BarGaugeNamePlacement } from '@grafana/schema';
import { measureText, calculateFontSize } from '../../utils/measureText.mjs';
import { clearButtonStyles } from '../Button/Button.mjs';
import { FormattedValueDisplay } from '../FormattedValueDisplay/FormattedValueDisplay.mjs';
const MIN_VALUE_HEIGHT = 18;
const MAX_VALUE_HEIGHT = 50;
const MAX_VALUE_WIDTH = 150;
const TITLE_LINE_HEIGHT = 1.5;
const VALUE_LINE_HEIGHT = 1;
const VALUE_LEFT_PADDING = 10;
const VALUE_RIGHT_OVERFLOW_PADDING = 15;
class BarGauge extends PureComponent {
render() {
const { onClick, className, theme } = this.props;
const { title } = this.props.value;
const styles = getTitleStyles(this.props);
if (onClick) {
return /* @__PURE__ */ jsxs(
"button",
{
type: "button",
style: styles.wrapper,
onClick,
className: cx(clearButtonStyles(theme), className),
children: [
/* @__PURE__ */ jsx("div", { style: styles.title, children: title }),
this.renderBarAndValue()
]
}
);
}
return /* @__PURE__ */ jsxs("div", { style: styles.wrapper, className, children: [
title && /* @__PURE__ */ jsx("div", { style: styles.title, children: title }),
this.renderBarAndValue()
] });
}
renderBarAndValue() {
switch (this.props.displayMode) {
case "lcd":
return this.renderRetroBars();
case "basic":
case "gradient":
default:
return this.renderBasicAndGradientBars();
}
}
renderBasicAndGradientBars() {
const { value, showUnfilled, valueDisplayMode } = this.props;
const styles = getBasicAndGradientStyles(this.props);
return /* @__PURE__ */ jsxs("div", { style: styles.wrapper, children: [
valueDisplayMode !== BarGaugeValueMode.Hidden && /* @__PURE__ */ jsx(
FormattedValueDisplay,
{
"data-testid": selectors.components.Panels.Visualization.BarGauge.valueV2,
value,
style: styles.value
}
),
showUnfilled && /* @__PURE__ */ jsx("div", { style: styles.emptyBar }),
/* @__PURE__ */ jsx("div", { style: styles.bar })
] });
}
renderRetroBars() {
var _a, _b;
const {
display,
field,
value,
itemSpacing,
alignmentFactors,
orientation,
lcdCellWidth,
text,
valueDisplayMode,
theme,
isOverflow
} = this.props;
const { valueHeight, valueWidth, maxBarHeight, maxBarWidth, wrapperWidth, wrapperHeight } = calculateBarAndValueDimensions(this.props);
const minValue = (_a = field.min) != null ? _a : GAUGE_DEFAULT_MINIMUM;
const maxValue = (_b = field.max) != null ? _b : GAUGE_DEFAULT_MAXIMUM;
const isVert = isVertical(orientation);
const valueRange = maxValue - minValue;
const maxSize = isVert ? maxBarHeight : maxBarWidth;
const cellSpacing = itemSpacing;
const cellCount = Math.floor(maxSize / lcdCellWidth);
const cellSize = Math.floor((maxSize - cellSpacing * cellCount) / cellCount);
const valueColor = getTextValueColor(this.props);
const valueToBaseSizeOn = alignmentFactors ? alignmentFactors : value;
const valueStyles = getValueStyles(
valueToBaseSizeOn,
valueColor,
valueWidth,
valueHeight,
orientation,
isOverflow,
text
);
const containerStyles = {
width: `${wrapperWidth}px`,
height: `${wrapperHeight}px`,
display: "flex"
};
if (isVert) {
containerStyles.flexDirection = "column-reverse";
containerStyles.alignItems = "center";
} else {
containerStyles.flexDirection = "row";
containerStyles.alignItems = "center";
valueStyles.justifyContent = "flex-end";
}
const cells = [];
for (let i = 0; i < cellCount; i++) {
const currentValue = minValue + valueRange / cellCount * i;
const cellColor = getCellColor(currentValue, value, display);
const cellStyles = {
borderRadius: theme.shape.radius.default
};
if (cellColor.isLit) {
cellStyles.backgroundImage = `radial-gradient(${cellColor.background} 10%, ${cellColor.backgroundShade})`;
} else {
cellStyles.backgroundColor = cellColor.background;
}
if (isVert) {
cellStyles.height = `${cellSize}px`;
cellStyles.width = `${maxBarWidth}px`;
cellStyles.marginTop = `${cellSpacing}px`;
} else {
cellStyles.width = `${cellSize}px`;
cellStyles.height = `${maxBarHeight}px`;
cellStyles.marginRight = `${cellSpacing}px`;
}
cells.push(/* @__PURE__ */ jsx("div", { style: cellStyles }, i.toString()));
}
return /* @__PURE__ */ jsxs("div", { style: containerStyles, children: [
cells,
valueDisplayMode !== BarGaugeValueMode.Hidden && /* @__PURE__ */ jsx(
FormattedValueDisplay,
{
"data-testid": selectors.components.Panels.Visualization.BarGauge.valueV2,
value,
style: valueStyles
}
)
] });
}
}
BarGauge.defaultProps = {
lcdCellWidth: 12,
value: {
text: "100",
numeric: 100
},
displayMode: BarGaugeDisplayMode.Gradient,
orientation: VizOrientation.Horizontal,
field: {
min: 0,
max: 100,
thresholds: {
mode: ThresholdsMode.Absolute,
steps: []
}
},
itemSpacing: 8,
showUnfilled: true,
isOverflow: false
};
function isVertical(orientation) {
return orientation === VizOrientation.Vertical;
}
function calculateTitleDimensions(props) {
var _a, _b;
const { height, width, alignmentFactors, orientation, text, namePlacement } = props;
const title = alignmentFactors ? alignmentFactors.title : props.value.title;
if (!title) {
return { fontSize: 0, width: 0, height: 0, placement: "above" };
}
if (namePlacement === BarGaugeNamePlacement.Hidden) {
return {
fontSize: 0,
width: 0,
height: 0,
placement: BarGaugeNamePlacement.Hidden
};
}
if (isVertical(orientation)) {
const fontSize = (_a = text == null ? void 0 : text.titleSize) != null ? _a : 14;
return {
fontSize,
width,
height: fontSize * TITLE_LINE_HEIGHT,
placement: "below"
};
}
const shouldDisplayValueAbove = height > 40 && namePlacement === BarGaugeNamePlacement.Auto || namePlacement === BarGaugeNamePlacement.Top;
if (shouldDisplayValueAbove) {
if (text == null ? void 0 : text.titleSize) {
return {
fontSize: text == null ? void 0 : text.titleSize,
width: 0,
height: text.titleSize * TITLE_LINE_HEIGHT,
placement: "above"
};
}
const maxTitleHeightRatio2 = 0.45;
const titleHeight2 = Math.max(Math.min(height * maxTitleHeightRatio2, MAX_VALUE_HEIGHT), 17);
return {
fontSize: titleHeight2 / TITLE_LINE_HEIGHT,
width: 0,
height: titleHeight2,
placement: "above"
};
}
const maxTitleHeightRatio = 0.6;
const titleHeight = Math.max(height * maxTitleHeightRatio, MIN_VALUE_HEIGHT);
const titleFontSize = titleHeight / TITLE_LINE_HEIGHT;
const textSize = measureText(title, titleFontSize);
const textWidth = Math.min(textSize.width + 15, width * 0.4);
return {
fontSize: (_b = text == null ? void 0 : text.titleSize) != null ? _b : titleFontSize,
height: 0,
width: textWidth,
placement: "left"
};
}
function getTitleStyles(props) {
const wrapperStyles = {
display: "flex",
overflow: "hidden",
width: "100%"
};
const titleDim = calculateTitleDimensions(props);
const titleStyles = {
fontSize: `${titleDim.fontSize}px`,
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
width: "100%",
alignItems: "center",
alignSelf: "center"
};
if (titleDim.placement === "hidden") {
titleStyles.display = "none";
} else {
if (isVertical(props.orientation)) {
wrapperStyles.flexDirection = "column-reverse";
titleStyles.textAlign = "center";
} else {
if (titleDim.placement === "above") {
wrapperStyles.flexDirection = "column";
} else {
wrapperStyles.flexDirection = "row";
titleStyles.width = `${titleDim.width}px`;
titleStyles.textAlign = "right";
titleStyles.paddingRight = "10px";
}
}
}
return {
wrapper: wrapperStyles,
title: titleStyles
};
}
function calculateBarAndValueDimensions(props) {
var _a;
const { height, width, orientation, text, alignmentFactors, valueDisplayMode } = props;
const titleDim = calculateTitleDimensions(props);
const value = alignmentFactors != null ? alignmentFactors : props.value;
const valueString = formattedValueToString(value);
let maxBarHeight = 0;
let maxBarWidth = 0;
let valueHeight = 0;
let valueWidth = 0;
let wrapperWidth = 0;
let wrapperHeight = 0;
const fontSizeToMeasureWith = (_a = text == null ? void 0 : text.valueSize) != null ? _a : Math.max(titleDim.fontSize, 12);
const realTextSize = measureText(valueString, fontSizeToMeasureWith);
const realValueWidth = realTextSize.width + VALUE_LEFT_PADDING * 2;
if (isVertical(orientation)) {
if (text == null ? void 0 : 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;
if (valueDisplayMode === BarGaugeValueMode.Hidden) {
valueHeight = 0;
valueWidth = 0;
}
maxBarHeight = height - (titleDim.height + valueHeight);
maxBarWidth = width;
wrapperWidth = width;
wrapperHeight = height - titleDim.height;
} else {
if (valueDisplayMode === BarGaugeValueMode.Hidden) {
valueHeight = 0;
valueWidth = 0;
} else {
valueHeight = height - titleDim.height;
valueWidth = Math.max(Math.min(width * 0.2, MAX_VALUE_WIDTH), realValueWidth);
}
maxBarHeight = height - titleDim.height;
maxBarWidth = width - valueWidth - titleDim.width;
if (titleDim.placement === "above") {
wrapperWidth = width;
wrapperHeight = height - titleDim.height;
} else {
wrapperWidth = width - titleDim.width;
wrapperHeight = height;
}
}
return {
valueWidth,
valueHeight,
maxBarWidth,
maxBarHeight,
wrapperHeight,
wrapperWidth
};
}
function getCellColor(positionValue, value, display) {
if (positionValue === null) {
return {
background: FALLBACK_COLOR,
border: FALLBACK_COLOR
};
}
const color = display ? display(positionValue).color : null;
if (color) {
if (value === null || isNaN(value.numeric) || positionValue !== null && positionValue > value.numeric) {
return {
background: tinycolor(color).setAlpha(0.18).toRgbString(),
border: "transparent",
isLit: false
};
} else {
return {
background: tinycolor(color).setAlpha(0.95).toRgbString(),
backgroundShade: tinycolor(color).setAlpha(0.55).toRgbString(),
border: tinycolor(color).setAlpha(0.9).toRgbString(),
isLit: true
};
}
}
return {
background: FALLBACK_COLOR,
border: FALLBACK_COLOR
};
}
function getValuePercent(value, minValue, maxValue) {
const valueRatio = Math.min((value - minValue) / (maxValue - minValue), 1);
return isNaN(valueRatio) ? 0 : valueRatio;
}
function getBasicAndGradientStyles(props) {
var _a, _b, _c;
const { displayMode, field, value, alignmentFactors, orientation, theme, text, isOverflow } = props;
const { valueWidth, valueHeight, maxBarHeight, maxBarWidth } = calculateBarAndValueDimensions(props);
const minValue = (_a = field.min) != null ? _a : GAUGE_DEFAULT_MINIMUM;
const maxValue = (_b = field.max) != null ? _b : GAUGE_DEFAULT_MAXIMUM;
const valuePercent = getValuePercent(value.numeric, minValue, maxValue);
const textColor = getTextValueColor(props);
const barColor = (_c = value.color) != null ? _c : FALLBACK_COLOR;
const valueToBaseSizeOn = alignmentFactors ? alignmentFactors : value;
const valueStyles = getValueStyles(
valueToBaseSizeOn,
textColor,
valueWidth,
valueHeight,
orientation,
isOverflow,
text
);
const isBasic = displayMode === "basic";
const wrapperStyles = {
display: "flex",
flexGrow: 1
};
const barStyles = {
borderRadius: theme.shape.radius.default,
position: "relative"
};
const emptyBar = {
background: theme.colors.background.secondary,
flexGrow: 1,
display: "flex",
borderRadius: theme.shape.radius.default,
position: "relative"
};
if (isVertical(orientation)) {
const barHeight = Math.max(valuePercent * maxBarHeight, 1);
wrapperStyles.flexDirection = "column";
wrapperStyles.justifyContent = "flex-end";
barStyles.transition = "height 1s";
barStyles.height = `${barHeight}px`;
barStyles.width = `${maxBarWidth}px`;
emptyBar.bottom = "-3px";
emptyBar.width = `${valueWidth}px`;
if (isBasic) {
barStyles.background = `${tinycolor(barColor).setAlpha(0.35).toRgbString()}`;
barStyles.borderTop = `2px solid ${barColor}`;
} else {
barStyles.background = getBarGradient(props, maxBarHeight);
}
} else {
const barWidth = Math.max(valuePercent * maxBarWidth, 1);
wrapperStyles.flexDirection = "row-reverse";
wrapperStyles.justifyContent = "flex-end";
wrapperStyles.alignItems = "stretch";
barStyles.transition = "width 1s";
barStyles.height = `${maxBarHeight}px`;
barStyles.width = `${barWidth}px`;
emptyBar.left = "-3px";
emptyBar.height = `${valueHeight}px`;
if (isBasic) {
barStyles.background = `${tinycolor(barColor).setAlpha(0.35).toRgbString()}`;
barStyles.borderRight = `2px solid ${barColor}`;
} else {
barStyles.background = getBarGradient(props, maxBarWidth);
}
}
return {
wrapper: wrapperStyles,
bar: barStyles,
value: valueStyles,
emptyBar
};
}
function getBarGradient(props, maxSize) {
var _a, _b;
const { field, value, orientation, theme } = props;
const cssDirection = isVertical(orientation) ? "0deg" : "90deg";
const minValue = field.min;
const maxValue = field.max;
let gradient = "";
let lastpos = 0;
let mode = getFieldColorMode((_a = field.color) == null ? void 0 : _a.mode);
if (mode.id === FieldColorModeId.Thresholds) {
const thresholds = field.thresholds;
for (let i = 0; i < thresholds.steps.length; i++) {
const threshold = thresholds.steps[i];
const color = props.theme.visualization.getColorByName(threshold.color);
const valuePercent = thresholds.mode === ThresholdsMode.Percentage ? threshold.value / 100 : getValuePercent(threshold.value, minValue, maxValue);
const pos = valuePercent * maxSize;
const offset = Math.round(pos - (pos - lastpos) / 2);
const thresholdValue = thresholds.mode === ThresholdsMode.Percentage ? minValue + (maxValue - minValue) * valuePercent : threshold.value;
if (gradient === "") {
gradient = `linear-gradient(${cssDirection}, ${color}, ${color}`;
} else if (value.numeric < thresholdValue) {
break;
} else {
lastpos = pos;
gradient += ` ${offset}px, ${color}`;
}
}
return gradient + ")";
}
if (mode.isContinuous && mode.getColors) {
const scheme = mode.getColors(theme);
for (let i = 0; i < scheme.length; i++) {
const color = scheme[i];
if (gradient === "") {
gradient = `linear-gradient(${cssDirection}, ${color} 0px`;
} else {
const valuePercent = i / (scheme.length - 1);
const pos = valuePercent * maxSize;
gradient += `, ${color} ${pos}px`;
}
}
return gradient + ")";
}
return (_b = value.color) != null ? _b : FALLBACK_COLOR;
}
function getTextValueColor(props) {
if (props.valueDisplayMode === "text") {
return props.theme.colors.text.primary;
}
const { value } = props;
if (value.color) {
return value.color;
}
return FALLBACK_COLOR;
}
function getValueStyles(value, color, width, height, orientation, isOverflow, text) {
var _a, _b;
const styles = {
color,
height: `${height}px`,
maxWidth: `${width}px`,
display: "flex",
alignItems: "center",
textWrap: "nowrap",
lineHeight: VALUE_LINE_HEIGHT
};
let textWidth = width;
const formattedValueString = formattedValueToString(value);
if (isVertical(orientation)) {
styles.fontSize = (_a = text == null ? void 0 : text.valueSize) != null ? _a : calculateFontSize(formattedValueString, textWidth, height, VALUE_LINE_HEIGHT);
styles.justifyContent = `center`;
} else {
styles.fontSize = (_b = text == null ? void 0 : text.valueSize) != null ? _b : 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 + (isOverflow ? VALUE_RIGHT_OVERFLOW_PADDING : 0)}px`;
textWidth -= VALUE_LEFT_PADDING;
}
return styles;
}
export { BarGauge, calculateBarAndValueDimensions, getBarGradient, getBasicAndGradientStyles, getCellColor, getTextValueColor, getTitleStyles, getValuePercent };
//# sourceMappingURL=BarGauge.mjs.map