<!doctype html> <html> <head> <meta charset="utf-8"> <meta http-equiv="x-ua-compatible" content="ie=edge"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> </head> <body> <svg id="svg"></svg> <button id="update">update</button> <button id="reset">reset</button> <script src="https://d3js.org/d3.v5.min.js"></script> <script> const defaults = { target: '#chart', width: 640, height: 480, margin: { top: 20, right: 20, bottom: 30, left: 40 } } class LineChart { constructor(config) { Object.assign(this, defaults, config) this.init() } init() { const {target, width, height, margin} = this const w = width - margin.left - margin.right const h = height - margin.top - margin.bottom this.svg = d3.select(target) .attr('width', width) .attr('height', height) .append('g') .attr('transform', `translate(${margin.left}, ${margin.top})`) this.xScale = d3.scalePoint() .range([0, w]) this.yScale = d3.scaleLinear() .range([h, 0]) this.xAxis = d3.axisBottom(this.xScale) this.svg .append('g') .attr('class', 'x axis') .attr('transform', `translate(0, ${h})`) .call(this.xAxis) this.yAxis = d3.axisLeft(this.yScale) this.svg .append('g') .attr('class', 'y axis') .call(this.yAxis) } render(data) { const {xScale, yScale, xAxis, yAxis, svg} = this xScale.domain(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k']) svg.select('.x.axis') .call(xAxis) yScale.domain([0, 10]) svg.select('.y.axis') .call(yAxis) svg .selectAll('.dot') // .data(data, d => d.key) .data(data) .enter() .append('circle') .attr('class', 'dot') .attr('cx', d => xScale(d.key)) .attr('cy', d => yScale(d.value)) .attr('r', 10) } update(data) { const {svg, xScale, yScale} = this // join new data with old elements const selection = svg .selectAll('.dot') // .data(data, d => d.key) .data(data) // update old elements selection .transition() .attr('cx', d => xScale(d.key)) .attr('cy', d => yScale(d.value)) // add new elements selection.enter() .append('circle') .attr('class', 'dot') .attr('cx', d => xScale(d.key)) .attr('cy', d => yScale(d.value)) .attr('r', 0) .transition() .attr('r', 10) .attr('fill', 'orange') // remove old elements selection.exit() .transition() .attr('r', 0) .attr('fill', 'red') .remove() } } // draw chart let chart const draw = () => { const config = { target: 'svg', width: document.body.clientWidth } chart = new LineChart(config) const data = [ {key: 'a', value: 1}, {key: 'b', value: 0}, {key: 'c', value: 3}, {key: 'd', value: 2}, {key: 'e', value: 5}, {key: 'f', value: 4}, {key: 'g', value: 7}, {key: 'h', value: 6}, {key: 'i', value: 9}, {key: 'j', value: 8} ] chart.render(data) } draw() // update button const button = document.getElementById('update') button.addEventListener('click', () => { const data = [ // remove a // {key: 'a', value: 1}, // update b, c, d, e, f, g, h, i, j {key: 'b', value: 1}, {key: 'c', value: 4}, {key: 'd', value: 3}, {key: 'e', value: 6}, {key: 'f', value: 3}, {key: 'g', value: 6}, {key: 'h', value: 7}, {key: 'i', value: 3}, {key: 'j', value: 4}, // add k {key: 'k', value: 9} ] chart.update(data) }) // reset button const reset = document.getElementById('reset') reset.addEventListener('click', () => { document.getElementById('svg').firstChild.remove() draw() }) </script> </body> </html>