Skip to content

Instantly share code, notes, and snippets.

@skrater
Created January 20, 2021 00:27
Show Gist options
  • Save skrater/92cdf950c5dd2e9b78f063632bae93e2 to your computer and use it in GitHub Desktop.
Save skrater/92cdf950c5dd2e9b78f063632bae93e2 to your computer and use it in GitHub Desktop.
React SVG Gauge
import React, {useMemo} from 'react';
const GaugeDefaults = {
centerX: 50,
centerY: 50,
};
/**
* Gets cartesian co-ordinates for a specified radius and angle (in degrees)
* @param cx {Number} The center x co-oriinate
* @param cy {Number} The center y co-ordinate
* @param radius {Number} The radius of the circle
* @param angle {Number} The angle in degrees
* @return An object with x,y co-ordinates
*/
function getCartesian(cx, cy, radius, angle) {
const rad = (angle * Math.PI) / 180;
return {
x: Math.round((cx + radius * Math.cos(rad)) * 1000) / 1000,
y: Math.round((cy + radius * Math.sin(rad)) * 1000) / 1000,
};
}
// Returns start and end points for dial
// i.e. starts at 135deg ends at 45deg with large arc flag
// REMEMBER!! angle=0 starts on X axis and then increases clockwise
function getDialCoords(radius, startAngle, endAngle) {
const cx = GaugeDefaults.centerX;
const cy = GaugeDefaults.centerY;
return {
end: getCartesian(cx, cy, radius, endAngle),
start: getCartesian(cx, cy, radius, startAngle),
};
}
function pathString(radius, startAngle, endAngle, largeArc) {
const coords = getDialCoords(radius, startAngle, endAngle);
const start = coords.start;
const end = coords.end;
const largeArcFlag = typeof largeArc === 'undefined' ? 1 : largeArc;
return [
'M',
start.x,
start.y,
'A',
radius,
radius,
0,
largeArcFlag,
1,
end.x,
end.y,
].join(' ');
}
function getValueInPercentage(value, min, max) {
const newMax = max - min;
const newVal = value - min;
return (100 * newVal) / newMax;
}
/**
* Translates percentage value to angle. e.g. If gauge span angle is 180deg, then 50%
* will be 90deg
*/
function getAngle(percentage, gaugeSpanAngle) {
return (percentage * gaugeSpanAngle) / 100;
}
function updateGauge(theValue, min, max, startAngle, endAngle, radius) {
const val = getValueInPercentage(theValue, min, max);
const angle = getAngle(val, 360 - Math.abs(startAngle - endAngle));
// this is because we are using arc greater than 180deg
const flag = angle <= 180 ? 0 : 1;
return pathString(radius, startAngle, angle + startAngle, flag);
}
const Gauge = ({value = 20, min = 0, max = 100}) => {
const opts = {
startAngle: 180,
endAngle: 0,
radius: 40,
};
const pathValue = useMemo(() => {
return updateGauge(
value,
min,
max,
opts.startAngle,
opts.endAngle,
opts.radius,
);
}, [value, min, max, opts]);
return (
<svg viewBox="0 0 100 100">
<defs>
<linearGradient
id="gradient"
gradientTransform="rotate(-90)"
x1="0%"
y1="0%"
x2="0%"
y2={`${(max / value) * 100}%`}>
<stop offset="0%" style={{stopColor: '#2AC695', stopOpacity: 1}} />
<stop offset="50%" style={{stopColor: '#E68B27', stopOpacity: 1}} />
<stop offset="100%" style={{stopColor: '#E92B46', stopOpacity: 1}} />
</linearGradient>
</defs>
<path
fill="none"
stroke="#E6E8EB"
stroke-width="2"
d="M 10 50 A 40 40 0 0 1 90 50"></path>
<g class="text-container">
<text
x="50"
y="50"
fill="#999"
font-size="100%"
font-family="sans-serif"
font-weight="normal"
text-anchor="middle"
alignment-baseline="middle"
dominant-baseline="central">
{value}
</text>
</g>
<path
fill="none"
style={{stroke: 'url(#gradient)'}}
stroke-width="2"
d={pathValue}></path>
</svg>
);
};
export default Gauge;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment