Skip to content

Instantly share code, notes, and snippets.

@volodalexey
Forked from d3noob/.block
Last active July 26, 2018 14:13
Show Gist options
  • Save volodalexey/70703a743ee38c6bb9d0090313ca7ea3 to your computer and use it in GitHub Desktop.
Save volodalexey/70703a743ee38c6bb9d0090313ca7ea3 to your computer and use it in GitHub Desktop.
D3 v5 ES6 Bars Chart (optimized for React or Vue)

Bars Chart Forked from https://gist.github.com/d3noob/bdf28027e0ce70bd132edc64f1dd7ea4

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 Bars Chart (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, min: d3Min, max: d3Max,
scaleBand: d3ScaleBand, scaleLinear: d3ScaleLinear,
axisBottom: d3AxisBottom, axisLeft: d3AxisLeft,
} = d3
const width = 500
const height = 500
const upperFill = '#f05757'
const lowerFill = '#8abe6e'
const leftAxisStroke = '#777777'
const DURATION = 1000
const {baseLine, data} = curData
const minValue = Math.min(d3Min(data, d => d.value), baseLine)
const maxValue = Math.max(d3Max(data, d => d.value), baseLine)
const margin = { top: 20, right: 20, bottom: 80, left: 60 }
const innerWidth = width - margin.left - margin.right
const innerHeight = height - margin.top - margin.bottom
// https://groups.google.com/forum/#!topic/d3-js/Rlv0O8xsFhs
const svgData = d3Select(wrapper).selectAll('svg').data(['dummy data'])
const svgEnter = svgData.enter().append('svg')
svgEnter.attr('width', width)
svgEnter.attr('height', height)
const gEnter = svgEnter.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr('class', 'bars-plot')
const svgMerge = svgData.merge(svgEnter)
const gMerge = svgMerge.selectAll('g.bars-plot')
const x = d3ScaleBand().range([0, innerWidth])
const y = d3ScaleLinear().range([innerHeight, 0])
// Scale the range of the data in the domains
x.domain(data.map(d => d.label));
y.domain([minValue, maxValue]);
// append the rectangles for the bar chart
const barWidth = x.bandwidth() / 2
const barXCorrect = barWidth / 2
const lowerBarData = gMerge.selectAll('rect.bar-lower').data(data)
const lowerBarEnter = lowerBarData.enter().append("rect")
.attr("class", "bar-lower")
.attr("fill", lowerFill)
const lowerBarMerge = lowerBarData.merge(lowerBarEnter)
lowerBarMerge
.attr("x", d => x(d.label) + barXCorrect)
.attr("width", barWidth)
.transition()
.duration(DURATION)
.attr('y', d => y(d.value > baseLine ? baseLine : d.value))
.attr('height', d => {
const height = innerHeight - y(d.value > baseLine ? baseLine : d.value)
return height < 0 ? 0 : height
})
const upperBarData = gMerge.selectAll('rect.bar-upper').data(data.filter(d => d.value > baseLine))
const upperBarEnter = upperBarData.enter().append("rect")
.attr("class", "bar-upper")
.attr("fill", upperFill)
const upperBarMerge = upperBarData.merge(upperBarEnter)
upperBarData.exit().remove()
upperBarMerge
.attr("x", d => x(d.label) + barXCorrect)
.attr("width", barWidth)
.transition()
.duration(DURATION)
.attr('y', d => y(d.value))
.attr("height", d => {
const height = (innerHeight - y(d.value)) - (innerHeight - y(baseLine))
return height < 0 ? 0 : height
})
// add the x Axis
gEnter.append('g')
.attr('class', 'x')
gMerge.select('g.x')
.attr("transform", "translate(0," + innerHeight + ")")
.transition()
.duration(DURATION)
.call(d3AxisBottom(x))
gMerge.selectAll('g.x text')
.attr('y', 0)
.attr('x', -9)
.attr('dy', '-.35em')
.attr('transform', 'rotate(-45)')
.style('text-anchor', 'end');
// add the y Axis
gEnter.append("g")
.attr('class', 'y')
gMerge
.select('g.y')
.transition()
.duration(DURATION)
.call(d3AxisLeft(y)
.tickSizeInner(-innerWidth)
.tickValues([minValue, baseLine, maxValue]))
.selectAll('text')
.attr('x', -10);
gMerge.selectAll('g.y .tick')
.attr("stroke", leftAxisStroke)
.attr("stroke-dasharray", "2,2");
}
function destroyChart (wrapper) {
const {select: d3Select} = d3
d3Select(wrapper).selectAll('*').remove()
}
document.addEventListener('DOMContentLoaded', () => {
function getDate () {
const date = new Date(1532000000000 + Math.round(Math.random()*999999999))
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`
}
setInterval(() => {
renderChart(document.querySelector('#wrapper'), {
data: [
{ value: Math.random()*1000, label: getDate() },
{ value: Math.random()*1000, label: getDate() },
{ value: Math.random()*1000, label: getDate() },
{ value: Math.random()*1000, label: getDate() },
{ value: Math.random()*1000, label: getDate() },
{ value: Math.random()*1000, label: getDate() },
{ value: Math.random()*1000, label: getDate() },
{ value: Math.random()*1000, label: getDate() },
{ value: Math.random()*1000, label: getDate() },
{ value: Math.random()*1000, label: getDate() },
],
baseLine: Math.random()*1000,
})
}, 5000)
renderChart(document.querySelector('#wrapper'), {
data: [
{ value: Math.random()*1000, label: getDate() },
{ value: Math.random()*1000, label: getDate() },
{ value: Math.random()*1000, label: getDate() },
{ value: Math.random()*1000, label: getDate() },
{ value: Math.random()*1000, label: getDate() },
{ value: Math.random()*1000, label: getDate() },
{ value: Math.random()*1000, label: getDate() },
{ value: Math.random()*1000, label: getDate() },
{ value: Math.random()*1000, label: getDate() },
{ value: Math.random()*1000, label: getDate() },
],
baseLine: Math.random()*1000,
})
})
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment