Stackbits
Components > Gauge Chart

đŸ•šī¸ Gauge Chart

The Gauge Chart is a sleek and interactive data visualization tool designed to showcase progress, performance, or ratings in a visually engaging way. With a smooth animated arc, vibrant gradient colors, and dynamic tick marks, this semi-circular chart makes tracking key metrics both intuitive and fun. Whether you're measuring customer satisfaction, performance scores, or financial health, the Gauge Chart provides a quick, at-a-glance overview with a modern and responsive design.

Preview

1.21.51.92.32.63.03.33.64.0

3.2

Excellent

Follow below steps 👇đŸģ

Install dependencies

1
npm i framer-motion

Component

Create a file gauge-chart.tsx in your components folder and paste this code

1
'use client';
2
3
import { motion } from 'framer-motion';
4
import React from 'react';
5
6
const GaugeChart = ({ score, min, max }: { score: number; min: number; max: number }) => {
7
// Ensure score is within bounds
8
const boundedScore = Math.max(min, Math.min(max, score));
9
10
// Calculate the angle (0 to 180 degrees)
11
const angle = ((boundedScore - min) / (max - min)) * 180;
12
13
// Arc length calculation
14
const RADIUS = 80;
15
const arcLength = (180 / 360) * (2 * Math.PI * RADIUS);
16
17
// Calculate score thresholds dynamically
18
const range = max - min;
19
const lowThreshold = min + range * 0.33;
20
const highThreshold = min + range * 0.66;
21
22
// Generate tick marks
23
const numTicks = 8;
24
const ticks = Array.from({ length: numTicks + 1 }, (_, i) => {
25
const tickAngle = (i * 180) / numTicks;
26
const tickRadians = (tickAngle * Math.PI) / 180;
27
const x1 = 100 + (RADIUS - 5) * Math.cos(tickRadians - Math.PI);
28
const y1 = 100 + (RADIUS - 5) * Math.sin(tickRadians - Math.PI);
29
const x2 = 100 + (RADIUS + 5) * Math.cos(tickRadians - Math.PI);
30
const y2 = 100 + (RADIUS + 5) * Math.sin(tickRadians - Math.PI);
31
const labelX = 100 + (RADIUS + 15) * Math.cos(tickRadians - Math.PI);
32
const labelY = 100 + (RADIUS + 15) * Math.sin(tickRadians - Math.PI);
33
const value = min + (i * (max - min)) / numTicks;
34
return { x1, y1, x2, y2, labelX, labelY, value };
35
});
36
37
// Helper function to get score status and its corresponding style
38
const getScoreStatus = () => {
39
if (boundedScore > highThreshold) {
40
return {
41
text: 'Excellent',
42
className:
43
'bg-gradient-to-r from-green-500/20 to-green-400/20 text-green-500 ring-1 ring-green-500/30 shadow-lg shadow-green-500/10'
44
};
45
}
46
if (boundedScore > lowThreshold) {
47
return {
48
text: 'Good',
49
className:
50
'bg-gradient-to-r from-orange-500/20 to-orange-400/20 text-orange-500 ring-1 ring-orange-500/30 shadow-lg shadow-orange-500/10'
51
};
52
}
53
return {
54
text: 'Poor',
55
className:
56
'bg-gradient-to-r from-red-500/20 to-red-400/20 text-red-500 ring-1 ring-red-500/30 shadow-lg shadow-red-500/10'
57
};
58
};
59
60
const scoreStatus = getScoreStatus();
61
62
return (
63
<div className="relative w-72 h-72 border-[4px] border-slate-600/20 rounded-full p-4 flex flex-col">
64
{/* SVG Gauge */}
65
<svg className="relative top-0 overflow-visible" viewBox="0 0 200 120">
66
<defs>
67
{/* Enhanced Gradient Definition */}
68
<linearGradient
69
id="gaugeGradient"
70
gradientUnits="userSpaceOnUse"
71
x1="20"
72
y1="100"
73
x2="180"
74
y2="100"
75
>
76
<stop offset="0%" stopColor="#ef4444" />
77
<stop offset="25%" stopColor="#f97316" />
78
<stop offset="50%" stopColor="#facc15" />
79
<stop offset="75%" stopColor="#84cc16" />
80
<stop offset="100%" stopColor="#22c55e" />
81
</linearGradient>
82
{/* Glow Filter */}
83
<filter id="glow">
84
<feGaussianBlur stdDeviation="2" result="coloredBlur" />
85
<feMerge>
86
<feMergeNode in="coloredBlur" />
87
<feMergeNode in="SourceGraphic" />
88
</feMerge>
89
</filter>
90
</defs>
91
92
{/* Background Arc with shadow */}
93
<path
94
d="M20,100 A80,80 0 0,1 180,100"
95
fill="none"
96
stroke="rgba(30, 41, 59, 0.2)"
97
strokeWidth="8"
98
strokeLinecap="round"
99
/>
100
<path
101
d="M20,100 A80,80 0 0,1 180,100"
102
fill="none"
103
stroke="#1e293b"
104
strokeWidth="3"
105
strokeLinecap="round"
106
/>
107
108
{/* Tick marks and labels */}
109
{ticks.map((tick, i) => (
110
<g key={i} className="text-slate-500">
111
<line
112
x1={tick.x1}
113
y1={tick.y1}
114
x2={tick.x2}
115
y2={tick.y2}
116
stroke="currentColor"
117
strokeWidth="2"
118
opacity="0.5"
119
/>
120
<text
121
x={tick.labelX}
122
y={tick.labelY}
123
textAnchor="middle"
124
fontSize="8"
125
fill="currentColor"
126
opacity="0.8"
127
>
128
{tick.value.toFixed(1)}
129
</text>
130
</g>
131
))}
132
133
{/* Progress Arc with glow */}
134
<motion.path
135
d="M20,100 A80,80 0 0,1 180,100"
136
fill="none"
137
stroke="url(#gaugeGradient)"
138
strokeWidth="3"
139
strokeDasharray={arcLength}
140
strokeDashoffset={arcLength * (1 - angle / 180)}
141
strokeLinecap="round"
142
filter="url(#glow)"
143
initial={{ strokeDashoffset: arcLength }}
144
animate={{ strokeDashoffset: arcLength * (1 - angle / 180) }}
145
transition={{ duration: 1, ease: 'easeOut' }}
146
/>
147
</svg>
148
149
{/* Enhanced Needle with glow */}
150
<div className="absolute inset-0 h-full w-full top-0 flex items-center justify-center">
151
<motion.div
152
className="w-1.5 h-20 bg-white/40 origin-bottom rounded-full mb-16"
153
style={{
154
transformOrigin: 'bottom center',
155
clipPath: 'polygon(50% 0, 100% 100%, 0 100%)',
156
background:
157
'radial-gradient(circle at bottom, rgba(240,240,240,0.8) 0%, rgba(240,240,240,0.2))'
158
}}
159
initial={{ rotate: -90 }}
160
animate={{ rotate: -90 + angle }}
161
transition={{ duration: 1, ease: 'easeOut' }}
162
/>
163
</div>
164
165
{/* Enhanced Score Display */}
166
<div className="absolute inset-0 text-center flex flex-col items-center justify-end h-full gap-1 w-full pb-10">
167
<p className="text-4xl font-semibold tracking-tight bg-clip-text text-transparent bg-gradient-to-r from-slate-200 to-slate-400">
168
{boundedScore.toFixed(1)}
169
</p>
170
<span
171
className={`px-4 py-1 rounded-full text-xs font-medium backdrop-blur-sm ${scoreStatus.className}`}
172
>
173
{scoreStatus.text}
174
</span>
175
</div>
176
</div>
177
);
178
};
179
180
export default GaugeChart;

Usage

1
<GaugeChart score={3.1} min={1.5} max={4} />

â­ī¸ Got a question or feedback?
Feel free to reach out!