Skip to content

Instantly share code, notes, and snippets.

@volodalexey
Forked from msqr/LICENSE
Last active January 13, 2023 09:41
Show Gist options
  • Save volodalexey/4e75aa8911c8bf764f1c169985c178b3 to your computer and use it in GitHub Desktop.
Save volodalexey/4e75aa8911c8bf764f1c169985c178b3 to your computer and use it in GitHub Desktop.
D3 v5 ES6 Circular Gauge (optimized for React or Vue)

Circular Gauge Forked from https://gist.github.com/msqr/3202712

optimized for React or Vue

It means you can invoke renderChart() as many times as you wish.

E.g. in React:

import React, { Component } from 'react'
import { renderChart, destroyChart } from '...'

class Chart extends Component {
  ...
  render () {
    return <div ref={(el) => this.$wrapper = el}/>
  }
  
  componentDidUpdate () {
    renderChart(this.$wrapper, this.props.data)
  }
  
  componentWillUnmount () {
    destroyChart(this.$wrapper)
  }
  ...
}

E.g. in Vue:

<template>
  <div ref="$wrapper"></div>
</template>

<script>
import { renderChart, destroyChart } from '...'

export default {
  name: 'chart',
  ...
  watch: {
    data () {
      renderChart(this.$refs.$wrapper, this.data)
    }
  },
  beforeDestroy () {
    destroyChart(this.$refs.$wrapper)
  }
  ...
}
</script>
<!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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment