import { RefProps, bindGlobalStyle, getGlobalStylesId } from 'lupine.web'; import { chartCommonCss, getChartColor, describeArc, polarToCartesian } from './chart-utils'; import { Tooltip } from '../tooltip'; export type GaugeChartProps = { title?: string; value: number; min?: number; max?: number; color?: string; width?: string | number; height?: string | number; aspectRatio?: number; valueFormatter?: (value: number) => string; }; export const GaugeChart = (props: GaugeChartProps) => { const globalCssId = getGlobalStylesId(chartCommonCss); bindGlobalStyle(globalCssId, chartCommonCss); const min = props.min !== undefined ? props.min : 0; const max = props.max !== undefined ? props.max : 100; const value = Math.max(min, Math.min(max, props.value)); // Clamp value const percentage = (value - min) / (max - min); // Gauge is usually a half circle (180 degrees) // Let's sweep from -90 to +90 (which maps nicely to polarToCartesian 270 to 90 degrees) // Our polarToCartesian treats 0 degree as pointing UP (12 o'clock). // So a half circle goes from 270 (-90 left) to 90 (right). const startAngle = 270; const endAngle = 90; const totalAngle = endAngle - startAngle; // 180 (Wait, 90 - 270 = -180. The describeArc helper expects end > start normally for slicing, let's just use 270 to 90 by using 270 and 90 + 360 = 450) // Actually, standard polarToCartesian 0 is up, 90 is right, 180 is down, 270 is left // So left to right semi-circle is 270 to 450 (which is 90) const angleStart = 270; const angleEnd = 450; const angleRange = angleEnd - angleStart; const valueAngle = angleStart + percentage * angleRange; const viewBoxSize = 400; // Make the radius noticeably smaller than viewBox to prevent clipping const radius = (viewBoxSize / 2) * 0.8; const centerX = viewBoxSize / 2; const centerY = viewBoxSize / 2 + 30; // shift down to fit semi circle, but not too far out of bounds const innerRadius = radius * 0.7; // Thick gauge const color = props.color || getChartColor(0); const formatValue = props.valueFormatter || ((val) => val.toString()); // Render Background Arc const bgPath = describeArc(centerX, centerY, radius, angleStart, angleEnd, innerRadius); // Render Value Arc // If percentage is 0, we shouldn't render an invalid arc const valPath = percentage > 0.001 ? describeArc(centerX, centerY, radius, angleStart, Math.min(valueAngle, angleEnd - 0.001), innerRadius) : ''; const ref: RefProps = { referToCssId: globalCssId, }; const ratio = props.aspectRatio ?? 16 / 9; const paddingTop = `${(1 / ratio) * 100}%`; const styleStr = `width: ${props.width || '100%'};`; return (