@@ -22,12 +22,12 @@ const GaugeChart: React.FC<GaugeChartProps> = ({
2222} ) => {
2323 // Clamp value to ensure it stays within range
2424 const clampedValue = Math . min ( max , Math . max ( min , value ) ) ;
25-
25+
2626 //, ... (existing code)
27-
27+
2828 // Calculate percentage (0 to 1) for needle position
2929 const normalizedValue = ( clampedValue - min ) / ( max - min ) ;
30-
30+
3131 // Angle for needle: -90 (left) to 90 (right)
3232 const angle = normalizedValue * 180 - 90 ;
3333
@@ -51,6 +51,17 @@ const GaugeChart: React.FC<GaugeChartProps> = ({
5151 } ;
5252 } ;
5353
54+ // Helper for dynamic precision based on step size
55+ const getPrecision = ( num : number ) => {
56+ if ( Number . isInteger ( num ) ) return 0 ;
57+ const str = num . toString ( ) ;
58+ const decimalPart = str . split ( '.' ) [ 1 ] || '' ;
59+ return Math . min ( decimalPart . length , 3 ) ; // Cap at 3 decimals to avoid floating point issues
60+ } ;
61+
62+ const step = ( max - min ) / 4 ;
63+ const precision = getPrecision ( step ) ;
64+
5465 // Generate ticks
5566 const ticks = [ 0 , 0.25 , 0.5 , 0.75 , 1 ] . map ( ( percent ) => {
5667 const val = min + ( max - min ) * percent ;
@@ -69,7 +80,7 @@ const GaugeChart: React.FC<GaugeChartProps> = ({
6980 y2 : end . y ,
7081 tx : textPos . x ,
7182 ty : textPos . y ,
72- value : Math . round ( val ) ,
83+ value : parseFloat ( val . toFixed ( precision ) ) ,
7384 angle : tickAngle
7485 } ;
7586 } ) ;
@@ -81,7 +92,7 @@ const GaugeChart: React.FC<GaugeChartProps> = ({
8192 if ( val <= 0 ) return "text-blue-500" ;
8293 if ( val < 40 ) return "text-cyan-600" ;
8394 if ( val < 70 ) return "text-orange-500" ;
84- return "text-red-600" ;
95+ return "text-red-600" ;
8596 } ;
8697
8798 const formatValue = ( v : number ) => {
@@ -101,7 +112,7 @@ const GaugeChart: React.FC<GaugeChartProps> = ({
101112 < stop offset = "100%" stopColor = "#ef4444" /> { /* Red */ }
102113 </ linearGradient >
103114 < filter id = "needleShadow" x = "-50%" y = "-50%" width = "200%" height = "200%" >
104- < feDropShadow dx = "1" dy = "1" stdDeviation = "2" floodOpacity = "0.3" />
115+ < feDropShadow dx = "1" dy = "1" stdDeviation = "2" floodOpacity = "0.3" />
105116 </ filter >
106117 </ defs >
107118
@@ -117,33 +128,33 @@ const GaugeChart: React.FC<GaugeChartProps> = ({
117128
118129 { /* Colored Gradient Arc */ }
119130 < path
120- d = "M 30 100 A 70 70 0 0 1 170 100"
121- fill = "none"
122- stroke = "url(#coldToHotGradient)"
123- strokeWidth = { strokeWidth }
124- strokeLinecap = "round"
131+ d = "M 30 100 A 70 70 0 0 1 170 100"
132+ fill = "none"
133+ stroke = "url(#coldToHotGradient)"
134+ strokeWidth = { strokeWidth }
135+ strokeLinecap = "round"
125136 />
126137
127138 { /* Ticks & Labels */ }
128139 { ticks . map ( ( tick , i ) => (
129- < g key = { i } >
130- < line
131- x1 = { tick . x1 } y1 = { tick . y1 }
132- x2 = { tick . x2 } y2 = { tick . y2 }
133- stroke = "currentColor"
134- strokeWidth = "1.5"
135- className = "text-muted-foreground/50"
136- />
137- < text
138- x = { tick . tx }
139- y = { tick . ty }
140- textAnchor = "middle"
141- alignmentBaseline = "middle"
142- className = "text-[10px] fill-muted-foreground font-medium"
143- >
144- { tick . value }
145- </ text >
146- </ g >
140+ < g key = { i } >
141+ < line
142+ x1 = { tick . x1 } y1 = { tick . y1 }
143+ x2 = { tick . x2 } y2 = { tick . y2 }
144+ stroke = "currentColor"
145+ strokeWidth = "1.5"
146+ className = "text-muted-foreground/50"
147+ />
148+ < text
149+ x = { tick . tx }
150+ y = { tick . ty }
151+ textAnchor = "middle"
152+ alignmentBaseline = "middle"
153+ className = "text-[10px] fill-muted-foreground font-medium"
154+ >
155+ { tick . value }
156+ </ text >
157+ </ g >
147158 ) ) }
148159
149160 { /* Needle Layer */ }
@@ -154,41 +165,41 @@ const GaugeChart: React.FC<GaugeChartProps> = ({
154165 transition : "transform 1.2s cubic-bezier(0.4, 0, 0.2, 1)" ,
155166 } }
156167 >
157- { /* Main triangular needle */ }
158- < path
159- d = "M 100 110 L 95 100 L 100 40 L 105 100 Z"
160- fill = "currentColor"
161- className = "text-foreground"
162- filter = "url(#needleShadow)"
163- />
164- { /* Center Pivot */ }
168+ { /* Main triangular needle */ }
169+ < path
170+ d = "M 100 110 L 95 100 L 100 40 L 105 100 Z"
171+ fill = "currentColor"
172+ className = "text-foreground"
173+ filter = "url(#needleShadow)"
174+ />
175+ { /* Center Pivot */ }
165176 < circle cx = "100" cy = "100" r = "5" className = "fill-foreground" />
166177 < circle cx = "100" cy = "100" r = "2" className = "fill-background" />
167178 </ g >
168179 </ svg >
169180 </ div >
170-
181+
171182 { /* Detail Value Display */ }
172183 { showValue && (
173184 < div className = "-mt-8 text-center flex flex-col items-center z-10 relative" >
174- < div className = "flex items-baseline gap-1" >
175- < span className = { `text-4xl font-bold tracking-tight ${ getValueColor ( value ) } ` } >
176- { formatValue ( value ) }
177- </ span >
178- < span className = "text-sm font-bold text-muted-foreground/80 mb-1" > { unit } </ span >
179- </ div >
180-
181- { timestamp && timestamp > 0 && (
182- < span className = "text-xs text-muted-foreground/60 mt-1" >
183- { new Date ( timestamp * 1000 ) . toLocaleString ( undefined , {
184- month : "short" ,
185- day : "numeric" ,
186- hour : "2-digit" ,
187- minute : "2-digit" ,
188- second : "2-digit" ,
189- } ) }
190- </ span >
191- ) }
185+ < div className = "flex items-baseline gap-1" >
186+ < span className = { `text-4xl font-bold tracking-tight ${ getValueColor ( value ) } ` } >
187+ { formatValue ( value ) }
188+ </ span >
189+ < span className = "text-sm font-bold text-muted-foreground/80 mb-1" > { unit } </ span >
190+ </ div >
191+
192+ { timestamp && timestamp > 0 && (
193+ < span className = "text-xs text-muted-foreground/60 mt-1" >
194+ { new Date ( timestamp * 1000 ) . toLocaleString ( undefined , {
195+ month : "short" ,
196+ day : "numeric" ,
197+ hour : "2-digit" ,
198+ minute : "2-digit" ,
199+ second : "2-digit" ,
200+ } ) }
201+ </ span >
202+ ) }
192203 </ div >
193204 ) }
194205 </ div >
0 commit comments