D3 Notes (outdated version)

Intro

Notes while learning D3

D3 Bar Chart

Bind Data To Rectangles

Let’s work on making a simple bar chart, given a very simple dataset. Remember that all these D3 methods can be view in the D3 API Documentation

let w = 500
let h = 100
let padding = 2
let dataset = [5, 10, 15, 20, 25]
let svg = d3
  .select("body") // create svg
  .append("svg")
  .attr("width", w)
  .attr("height", h)

svg
  .selectAll("rect") // select multiple descendants for each selected element.
  .data(dataset) // bind elements to data
  .enter() // get the enter selection (data missing elements).
  .append("rect") // create rectangles
  .attr("x", 0) // x co-ordinate
  .attr("y", 0) // y co-ordinate
  .attr("width", 20)
  .attr("height", 100)

So now we have created a rectangle in our svg for each piece of data in our dataset. However we have hard coded the x and y co-ordinates, and width and height for each of them. Therefore they are all stacked on top of each other.

stacked recs

Dynamic Co-ordinates

Things get interesting when we start to change these positioning and size elements dynamically. Let’s start by changing the rectangles x-coordinates dynamically.

let w = 500
let h = 100
let padding = 2
let dataset = [5, 10, 15, 20, 25]
let svg = d3.select("body").append("svg").attr("width", w).attr("height", h)

svg
  .selectAll("rect")
  .data(dataset)
  .enter()
  .append("rect")
  .attr("x", function (d, i) {
    return i * 21
  })
  .attr("y", 0)
  .attr("width", 20)
  .attr("height", 100)

We have put in a little inline function that takes d and i as arguments. In D3 we use d to refer to the dataset, and then i as is typical, refers to the iteration or item. So we are looping over the dataset and for each item returning an x coordinate that is 21px larger than the one before it. We chose 21 for this number because each rectangle has a width of 20 and we are leaving them a 1px margin for visibility. That gives us this.

side by side rectangles

Which is a start, but we actually want to space the bars evenly according to the width of the svg. So we can take the width of the svg and divide it by the number of items in the dataset.

let w = 500
let h = 100
let padding = 2
let dataset = [5, 10, 15, 20, 25]
let svg = d3.select("body").append("svg").attr("width", w).attr("height", h)

svg
  .selectAll("rect")
  .data(dataset)
  .enter()
  .append("rect")
  .attr("x", function (d, i) {
    return i * (w / dataset.length)
  })
  .attr("y", 0)
  .attr("width", 20)
  .attr("height", 100)

which give us this.

evenly spaced

Dynamic Shape

Now we can adjust the width of the bars to fill the space more evenly.

let w = 500
let h = 100
let padding = 5
let dataset = [5, 10, 15, 20, 25]
let svg = d3.select("body").append("svg").attr("width", w).attr("height", h)

svg
  .selectAll("rect")
  .data(dataset)
  .enter()
  .append("rect")
  .attr("x", function (d, i) {
    return i * (w / dataset.length)
  })
  .attr("y", 0)
  .attr("width", w / dataset.length - padding)
  .attr("height", 100)

better width

Now if we try to set the height of each bar to it’s own number we hit a bit of a snag

let w = 500
let h = 100
let padding = 5
let dataset = [10, 40, 30, 15, 60, 80, 100]
let svg = d3.select("body").append("svg").attr("width", w).attr("height", h)

svg
  .selectAll("rect")
  .data(dataset)
  .enter()
  .append("rect")
  .attr("x", function (d, i) {
    return i * (w / dataset.length)
  })
  .attr("y", 0)
  .attr("width", w / dataset.length - padding)
  .attr("height", function (d) {
    return d
  })

The bars are hanging from the ceiling.

bars hanging from ceiling

This is because of the way the coordinate system in svg works, starting in the top left. So we need to change the y-coordinate for each rectangle.

let w = 500
let h = 100
let padding = 5
let dataset = [10, 40, 30, 15, 60, 80, 100]
let svg = d3.select("body").append("svg").attr("width", w).attr("height", h)

svg
  .selectAll("rect")
  .data(dataset)
  .enter()
  .append("rect")
  .attr("x", function (d, i) {
    return i * (w / dataset.length)
  })
  .attr("y", function (d) {
    return h - d
  })
  .attr("width", w / dataset.length - padding)
  .attr("height", function (d) {
    return d
  })

flipped

It’s starting to look like we’ve got something here!

Dynamic Color

We can add a bit of dynamic color easily

let w = 500
let h = 100
let padding = 5
let dataset = [10, 40, 30, 15, 60, 80, 100]
let svg = d3.select("body").append("svg").attr("width", w).attr("height", h)

svg
  .selectAll("rect")
  .data(dataset)
  .enter()
  .append("rect")
  .attr("x", function (d, i) {
    return i * (w / dataset.length)
  })
  .attr("y", function (d) {
    return h - d
  })
  .attr("width", w / dataset.length - padding)
  .attr("height", function (d) {
    return d
  })
  .attr("fill", function (d) {
    return `rgb(0,${d * 2},0)`
  })

dynamic color