Created
January 20, 2021 00:27
-
-
Save skrater/92cdf950c5dd2e9b78f063632bae93e2 to your computer and use it in GitHub Desktop.
React SVG Gauge
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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