Gauge: Fit-and-finish tweaks to glows, text position, and sparkline size (#115173)

* Gauge: Fit-and-finish tweaks to glows, text position, and sparkline size

* adjust text height and positions a little more

* cohesive no data handling

* more tweaks

* fix migration test

* Fix JSON formatting by adding missing newline

* remove new line
This commit is contained in:
Paul Marbach
2025-12-12 11:10:56 -05:00
committed by GitHub
parent 7114b9cd3b
commit c0dc92e8cd
7 changed files with 389 additions and 124 deletions
@@ -106,6 +106,11 @@ export function RadialGauge(props: RadialGaugeProps) {
const gaugeId = useId();
const styles = useStyles2(getStyles);
let effectiveTextMode = textMode;
if (effectiveTextMode === 'auto') {
effectiveTextMode = vizCount === 1 ? 'value' : 'value_and_name';
}
const startAngle = shape === 'gauge' ? 250 : 0;
const endAngle = shape === 'gauge' ? 110 : 360;
@@ -188,7 +193,7 @@ export function RadialGauge(props: RadialGaugeProps) {
// These elements are only added for first value / bar
if (barIndex === 0) {
if (glowBar) {
defs.push(<GlowGradient key="glow-filter" id={glowFilterId} radius={dimensions.radius} />);
defs.push(<GlowGradient key="glow-filter" id={glowFilterId} barWidth={dimensions.barWidth} />);
}
if (glowCenter) {
@@ -198,14 +203,14 @@ export function RadialGauge(props: RadialGaugeProps) {
graphics.push(
<RadialText
key="radial-text"
vizCount={vizCount}
textMode={textMode}
textMode={effectiveTextMode}
displayValue={displayValue.display}
dimensions={dimensions}
theme={theme}
valueManualFontSize={props.valueManualFontSize}
nameManualFontSize={props.nameManualFontSize}
shape={shape}
sparkline={displayValue.sparkline}
/>
);
@@ -254,6 +259,7 @@ export function RadialGauge(props: RadialGaugeProps) {
theme={theme}
color={color}
shape={shape}
textMode={effectiveTextMode}
/>
);
}
@@ -1,11 +1,9 @@
import { css } from '@emotion/css';
import { FieldDisplay, GrafanaTheme2, FieldConfig } from '@grafana/data';
import { GraphFieldConfig, GraphGradientMode, LineInterpolation } from '@grafana/schema';
import { Sparkline } from '../Sparkline/Sparkline';
import { RadialShape } from './RadialGauge';
import { RadialShape, RadialTextMode } from './RadialGauge';
import { GaugeDimensions } from './utils';
interface RadialSparklineProps {
@@ -14,23 +12,22 @@ interface RadialSparklineProps {
theme: GrafanaTheme2;
color?: string;
shape?: RadialShape;
textMode: Exclude<RadialTextMode, 'auto'>;
}
export function RadialSparkline({ sparkline, dimensions, theme, color, shape }: RadialSparklineProps) {
export function RadialSparkline({ sparkline, dimensions, theme, color, shape, textMode }: RadialSparklineProps) {
const { radius, barWidth } = dimensions;
if (!sparkline) {
return null;
}
const { radius, barWidth } = dimensions;
const height = radius / 4;
const widthFactor = shape === 'gauge' ? 1.6 : 1.4;
const width = radius * widthFactor - barWidth;
const topPos = shape === 'gauge' ? `${dimensions.gaugeBottomY - height}px` : `calc(50% + ${radius / 2.8}px)`;
const styles = css({
position: 'absolute',
top: topPos,
});
const showNameAndValue = textMode === 'value_and_name';
const height = radius / (showNameAndValue ? 4 : 3);
const width = radius * (shape === 'gauge' ? 1.6 : 1.4) - barWidth;
const topPos =
shape === 'gauge'
? `${dimensions.gaugeBottomY - height}px`
: `calc(50% + ${radius / (showNameAndValue ? 3.3 : 4)}px)`;
const config: FieldConfig<GraphFieldConfig> = {
color: {
@@ -45,7 +42,7 @@ export function RadialSparkline({ sparkline, dimensions, theme, color, shape }:
};
return (
<div className={styles}>
<div style={{ position: 'absolute', top: topPos }}>
<Sparkline height={height} width={width} sparkline={sparkline} theme={theme} config={config} />
</div>
);
@@ -1,6 +1,12 @@
import { css } from '@emotion/css';
import { DisplayValue, DisplayValueAlignmentFactors, formattedValueToString, GrafanaTheme2 } from '@grafana/data';
import {
DisplayValue,
DisplayValueAlignmentFactors,
FieldSparkline,
formattedValueToString,
GrafanaTheme2,
} from '@grafana/data';
import { useStyles2 } from '../../themes/ThemeContext';
import { calculateFontSize } from '../../utils/measureText';
@@ -8,21 +14,13 @@ import { calculateFontSize } from '../../utils/measureText';
import { RadialShape, RadialTextMode } from './RadialGauge';
import { GaugeDimensions } from './utils';
// function toCartesian(centerX: number, centerY: number, radius: number, angleInDegrees: number) {
// let radian = ((angleInDegrees - 90) * Math.PI) / 180.0;
// return {
// x: centerX + radius * Math.cos(radian),
// y: centerY + radius * Math.sin(radian),
// };
// }
interface RadialTextProps {
displayValue: DisplayValue;
theme: GrafanaTheme2;
dimensions: GaugeDimensions;
textMode: RadialTextMode;
vizCount: number;
textMode: Exclude<RadialTextMode, 'auto'>;
shape: RadialShape;
sparkline?: FieldSparkline;
alignmentFactors?: DisplayValueAlignmentFactors;
valueManualFontSize?: number;
nameManualFontSize?: number;
@@ -33,8 +31,8 @@ export function RadialText({
theme,
dimensions,
textMode,
vizCount,
shape,
sparkline,
alignmentFactors,
valueManualFontSize,
nameManualFontSize,
@@ -46,10 +44,6 @@ export function RadialText({
return null;
}
if (textMode === 'auto') {
textMode = vizCount === 1 ? 'value' : 'value_and_name';
}
const nameToAlignTo = (alignmentFactors ? alignmentFactors.title : displayValue.title) ?? '';
const valueToAlignTo = formattedValueToString(alignmentFactors ? alignmentFactors : displayValue);
@@ -59,7 +53,7 @@ export function RadialText({
// Not sure where this comes from but svg text is not using body line-height
const lineHeight = 1.21;
const valueWidthToRadiusFactor = 0.85;
const valueWidthToRadiusFactor = 0.82;
const nameToHeightFactor = 0.45;
const largeRadiusScalingDecay = 0.86;
@@ -98,18 +92,23 @@ export function RadialText({
const valueHeight = valueFontSize * lineHeight;
const nameHeight = nameFontSize * lineHeight;
const valueY = showName ? centerY - nameHeight / 2 : centerY;
const valueNameSpacing = valueHeight / 3.5;
const nameY = showValue ? valueY + valueHeight / 2 + valueNameSpacing : centerY;
const valueY = showName ? centerY - nameHeight * 0.3 : centerY;
const nameY = showValue ? valueY + valueHeight * 0.7 : centerY;
const nameColor = showValue ? theme.colors.text.secondary : theme.colors.text.primary;
const suffixShift = (valueFontSize - unitFontSize * 1.2) / 2;
// For gauge shape we shift text up a bit
const valueDy = shape === 'gauge' ? -valueFontSize * 0.3 : 0;
const nameDy = shape === 'gauge' ? -nameFontSize * 0.7 : 0;
// adjust the text up on gauges and when sparklines are present
let yOffset = 0;
if (shape === 'gauge') {
// we render from the center of the gauge, so move up by half of half of the total height
yOffset -= (valueHeight + nameHeight) / 4;
}
if (sparkline) {
yOffset -= 8;
}
return (
<g>
<g transform={`translate(0, ${yOffset})`}>
{showValue && (
<text
x={centerX}
@@ -119,7 +118,6 @@ export function RadialText({
className={styles.text}
textAnchor="middle"
dominantBaseline="middle"
dy={valueDy}
>
<tspan fontSize={unitFontSize}>{displayValue.prefix ?? ''}</tspan>
<tspan>{displayValue.text}</tspan>
@@ -133,7 +131,6 @@ export function RadialText({
fontSize={nameFontSize}
x={centerX}
y={nameY}
dy={nameDy}
textAnchor="middle"
dominantBaseline="middle"
fill={nameColor}
@@ -4,11 +4,12 @@ import { GaugeDimensions } from './utils';
export interface GlowGradientProps {
id: string;
radius: number;
barWidth: number;
}
export function GlowGradient({ id, radius }: GlowGradientProps) {
const glowSize = 0.02 * radius;
export function GlowGradient({ id, barWidth }: GlowGradientProps) {
// 0.75 is the minimum glow size, and it scales with bar width
const glowSize = 0.75 + barWidth * 0.08;
return (
<filter id={id} filterUnits="userSpaceOnUse">
@@ -82,7 +83,7 @@ export function MiddleCircleGlow({ dimensions, gaugeId, color }: CenterGlowProps
<>
<defs>
<radialGradient id={gradientId} r={'50%'} fr={'0%'}>
<stop offset="0%" stopColor={color} stopOpacity={0.2} />
<stop offset="0%" stopColor={color} stopOpacity={0.15} />
<stop offset="90%" stopColor={color} stopOpacity={0} />
</radialGradient>
</defs>