Updating the Chart

D3 can handle updating selections and scales without having to rewrite a majority of the code.

In this lesson, we are going to update the chart based on the user’s selection. This is where things are going to get tricky. We have to decide what gets updated and what does not get updated.

Press + to interact
async function draw() {
// Data
const dataset = await d3.json('/udata/1kL2GBJw8VB/data-4-1.json')
const xAccessor = d => d.currently.humidity
const yAccessor = d => d.length
// Dimensions
let dimensions = {
width: 800,
height: 400,
margins: 50
};
dimensions.ctrWidth = dimensions.width - dimensions.margins * 2
dimensions.ctrHeight = dimensions.height - dimensions.margins * 2
// Draw Image
const svg = d3.select('#chart')
.append("svg")
.attr("width", dimensions.width)
.attr("height", dimensions.height)
const ctr = svg.append("g") // <g>
.attr(
"transform",
`translate(${dimensions.margins}, ${dimensions.margins})`
)
// Create Scale
const xScale = d3.scaleLinear()
.domain(d3.extent(dataset, xAccessor))
.range([0, dimensions.ctrWidth])
.nice()
const bin = d3.bin()
.domain(xScale.domain())
.value(xAccessor)
.thresholds(10)
const newDataset = bin(dataset)
const padding = 1
const yScale = d3.scaleLinear()
.domain([0, d3.max(newDataset, yAccessor)])
.range([dimensions.ctrHeight, 0])
.nice()
// Draw Bars
ctr.selectAll('rect')
.data(newDataset)
.join('rect')
.attr('width', d => d3.max([0, xScale(d.x1) - xScale(d.x0) - padding]))
.attr("height", d => dimensions.ctrHeight - yScale(yAccessor(d)))
.attr('x', d => xScale(d.x0))
.attr("y", d => yScale(yAccessor(d)))
.attr('fill', '#01c5c4')
ctr.append('g')
.classed('bar-labels', true)
.selectAll('text')
.data(newDataset)
.join('text')
.attr('x', d => xScale(d.x0) + (xScale(d.x1) - xScale(d.x0)) / 2)
.attr('y', d => yScale(yAccessor(d)) - 10)
.text(yAccessor)
// Draw Axis
const xAxis = d3.axisBottom(xScale)
const xAxisGroup = ctr.append("g")
.style("transform", `translateY(${dimensions.ctrHeight}px)`)
xAxisGroup.call(xAxis)
function histogram(metric) {
}
d3.select("#metric").on('change', function (e) {
e.preventDefault()
histogram(this.value)
})
}
draw()

For example, if we scroll to the top of the function, we are making a request for the file. We do not need to repeat this request every time we change metrics. We only need the data once.

There are other parts we will need to update, such as the scales. We are going to move the things that need to be updated into the histogram() function. Code that does not need to be repeated will remain where it is been written.

Refactoring code

We will start from the top and work our way down. After performing a request for a file, we are creating some accessor functions. The accessor functions will need to change based on the current metric. They are functions that will tell D3 which properties it should use for drawing the chart. We will move the accessor functions to the histogram() function.

Press + to interact
function histogram(metric) {
const xAccessor = d => d.currently[metric]
const yAccessor = d => d.length
}

The accessor functions will need to be updated. Specifically, the xAccessor function will need to be told which metric to return. We have an argument in the histogram function called metric. We can use this argument to ...