Source

Components/D3/RadialChart.jsx

import * as d3 from 'd3'
import { useEffect, useRef } from 'react'
import useViewport from '../../utils/Hooks/useViewport'
import PropTypes from 'prop-types'

/**
 * A Radial chart showing an user score corresponding to the percentage of his weekly target. Filled by D3.js.
 * @name RadialChart
 * @param {Object} props - props component
 * @param {Array<Object>} props.data - user data
 * @param {number} props.svgHeight - height of svg container
 * @returns {ReactElement} a Radial Chart
 * @component
 */
export default function RadialChart({ data, svgHeight }) {
	//svg parent ref
	const radialContainerRef = useRef()
	//ref for resize event
	const updateLines = useRef(false)
	//responsive width
	const { viewportWidth } = useViewport()

	useEffect(() => {
		//if resize remove the previous chart
		updateLines.current
			? d3.select('.radial-chart-svg').remove()
			: (updateLines.current = true)
		DrawChart(data)

		// eslint-disable-next-line react-hooks/exhaustive-deps
	}, [data, viewportWidth])

	const margin = { top: 40, left: 30, right: 30, bottom: 20 }

	const DrawChart = () => {
		//dimentions
		const graphWidth = parseInt(d3.select(radialContainerRef.current).style('width')) - margin.left - margin.right
		// create new chart
		const svg = d3.select(radialContainerRef.current)
			.append("svg")
			.classed("radial-chart-svg", true)
			.attr('width', "100%")
			.attr("height", svgHeight)
			.style("background-color", "#F5F7F9")
			.style("border-radius", "5px")
		// add a title
		svg.append('text')
			.attr('fill', '#000')
			.attr('x', margin.left)
			.attr('y', margin.top)
			.text("Score")
			.style("font-size", "1.5rem")
			.style("font-weight", "500")
		//Draw the Circle
		svg.append("circle")
			.attr("transform", `translate(${graphWidth /2 + margin.right}, ${svgHeight /2 + margin.bottom})`)
			// .attr("cx", 0)
			// .attr("cy", 0)
			.attr("r", 90)
			.attr("fill", "#fff");
		//center text
		svg.append("text")
			.attr("fill", "#000")
			.attr("x", "50%")
			.attr("y", "50%")
			.style("text-anchor", "middle")
			.style("font-size", "1.8rem")
			.style("font-weight", "500")
			.text(`${data*100}%`)
		svg.append("text")
			.attr("fill", "#000")
			.attr("x", "50%")
			.attr("y", "62%")
			.style("font-size", "1.2rem")
			.style("font-weight", "400")
			.style("text-anchor", "middle")
			.text(`de votre`)
		svg.append("text")
			.attr("fill", "#000")
			.attr("x", "50%")
			.attr("y", "73%")
			.style("text-anchor", "middle")
			.style("font-size", "1.2rem")
			.style("font-weight", "400")
			.text(`objectif`)
		//
		const graph = svg.append("g")
			.attr("transform", `translate(${graphWidth /2 + margin.right}, ${svgHeight /2 + margin.bottom})`)

		const arcPath= d3.arc()
			.outerRadius(100)
			.innerRadius(90)
			.startAngle(0)
			.cornerRadius(8)

		graph.append("path") 
			.datum({ endAngle: -0.1 })
			.attr("d", arcPath)
			.attr("fill", "#FF0000")
			.transition()
            .duration(750)
            .call(arcTween, data * Math.PI * -2)

		function arcTween(transition, newFinishAngle) {
			transition.attrTween("d", function (d) {
				let interpolateEnd = d3.interpolate(d.endAngle, newFinishAngle)
				return function (t) {
					d.endAngle = interpolateEnd(t)
					return arcPath(d)
				}
			})
		}
	}

	return <div className="radial-chart-container" ref={radialContainerRef}></div>
}

RadialChart.propTypes = {
	data: PropTypes.number.isRequired,
	svgHeight: PropTypes.number,
}