|
<!DOCTYPE html> |
|
<html lang='en'> |
|
<head> |
|
<meta charset='UTF-8'> |
|
<title>D3 v5 ES6 Circular Gauge (optimized for React or Vue)</title> |
|
<script src='//d3js.org/d3.v5.min.js'></script> |
|
</head> |
|
<body> |
|
<div id='wrapper'></div> |
|
<script> |
|
function renderChart (wrapper, curData) { |
|
if (!wrapper) { |
|
return |
|
} |
|
const { select: d3Select, range: d3Range, rgb: d3Rgb, |
|
scaleLinear: d3ScaleLinear, line: d3Line, curveLinear: d3CurveLinear, |
|
arc: d3Arc, interpolateHsl: d3InterpolateHsl, easeElastic: d3EaseElastic, |
|
} = d3 |
|
|
|
const width = 500 |
|
const height = 250 |
|
const minValue = curData.minValue |
|
const maxValue = curData.maxValue |
|
const curValue = curData.curValue |
|
|
|
const radius = height - 10 |
|
const majorTicks = 5 |
|
const DURATION = 1500 |
|
const labelInset = 15 |
|
const ringWidth = 60 |
|
const ringInset = 20 |
|
const minAngle = -90 |
|
const maxAngle = 90 |
|
const range = maxAngle - minAngle |
|
const arcColorFn = d3InterpolateHsl(d3Rgb('#8abe6e'), d3Rgb('#f05757')) |
|
const arrowColor = '#dae7f5' |
|
|
|
const arc = d3Arc() |
|
.innerRadius(radius - ringWidth - ringInset) |
|
.outerRadius(radius - ringInset) |
|
.startAngle((d, i) => { |
|
const ratio = d * i |
|
return deg2rad(minAngle + (ratio * range)) |
|
}) |
|
.endAngle((d, i) => { |
|
const ratio = d * (i + 1) |
|
return deg2rad(minAngle + (ratio * range)) |
|
}) |
|
|
|
const svgData = d3Select(wrapper).selectAll('svg').data(['dummy data']) |
|
const tickData = d3Range(majorTicks).map(() => 1/majorTicks) |
|
|
|
const svgEnter = svgData.enter().append('svg') |
|
svgEnter.attr('width', width) |
|
svgEnter.attr('height', height) |
|
const svgMerge = svgData.merge(svgEnter) |
|
|
|
const centerTx = centerTranslation(radius) |
|
// sections |
|
const arcsData = svgMerge.selectAll('g.arc').data(tickData) |
|
const arcsEnter = arcsData.enter() |
|
.append('g') |
|
.attr('class', 'arc') |
|
.attr('transform', centerTx) |
|
|
|
arcsEnter.append('path') |
|
.attr('fill', (d, i) => arcColorFn(d * (i + 1))) |
|
.attr('d', arc) |
|
|
|
arcsData.merge(arcsEnter) |
|
|
|
// labels on sections |
|
const scaleValue = d3ScaleLinear() |
|
.range([0, 1]) |
|
.domain([minValue, maxValue]) |
|
const ticks = scaleValue.ticks(majorTicks) |
|
|
|
const labelsData = svgMerge.selectAll('g.label').data(ticks) |
|
const labelsEnter = labelsData.enter() |
|
.append('g') |
|
.attr('class', 'label') |
|
.attr('transform', centerTx) |
|
|
|
labelsData.exit().remove() |
|
|
|
labelsEnter |
|
.append('text') |
|
.text(d => d) |
|
|
|
const labelsMerge = labelsData.merge(labelsEnter) |
|
|
|
labelsMerge.select('text') |
|
.text(d => d) |
|
.transition() |
|
.duration(DURATION) |
|
.attr('transform', (d) => { |
|
const ratio = scaleValue(d) |
|
const newAngle = minAngle + (ratio * range) |
|
return 'rotate(' + newAngle + ') translate(0,' + (labelInset - radius) + ')' |
|
}) |
|
|
|
const pointerWidth = 10 |
|
const pointerHeadLengthPercent = 0.9 |
|
const pointerHeadLength = Math.round(radius * pointerHeadLengthPercent) |
|
const pointerTailLength = 5 |
|
const lineData = [[pointerWidth / 2, 0], |
|
[0, -pointerHeadLength], |
|
[-(pointerWidth / 2), 0], |
|
[0, pointerTailLength], |
|
[pointerWidth / 2, 0]] |
|
|
|
const pointerLine = d3Line().curve(d3CurveLinear) |
|
const pointerData = svgMerge.selectAll('g.pointer').data([lineData]) |
|
const pointerEnter = pointerData.enter() |
|
.append('g') |
|
.attr('class', 'pointer') |
|
.attr('transform', centerTx) |
|
|
|
pointerEnter.append('path') |
|
.attr('d', pointerLine) |
|
.attr('transform', 'rotate(' + minAngle + ')') |
|
.attr('fill', arrowColor) |
|
|
|
const pointerMerge = pointerData.merge(pointerEnter) |
|
const ratio = scaleValue(curValue) |
|
const newAngle = minAngle + (ratio * range) |
|
pointerMerge.select('path') |
|
.transition() |
|
.duration(DURATION) |
|
.ease(d3EaseElastic) |
|
.attr('transform', 'rotate(' + newAngle + ')') |
|
} |
|
|
|
function destroyChart (wrapper) { |
|
const {select: d3Select} = d3 |
|
d3Select(wrapper).selectAll('*').remove() |
|
} |
|
|
|
function centerTranslation (r) { |
|
return 'translate(' + r + ',' + r + ')' |
|
} |
|
|
|
function deg2rad (deg) { |
|
return deg * Math.PI / 180 |
|
} |
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
setInterval(() => { |
|
const maxValue = 500 + Math.random()*500 |
|
const minValue = Math.random()*500 |
|
renderChart(document.querySelector('#wrapper'), { |
|
minValue, curValue: minValue + Math.random()*(maxValue - minValue), maxValue, |
|
}) |
|
}, 5000) |
|
|
|
const maxValue = 500 + Math.random()*500 |
|
const minValue = Math.random()*500 |
|
renderChart(document.querySelector('#wrapper'), { |
|
minValue, curValue: minValue + Math.random()*(maxValue - minValue), maxValue, |
|
}) |
|
}) |
|
</script> |
|
</body> |
|
</html> |