Basic Setup

Posted .

Before we can start rendering our data we should first do some basic setup such as loading the data, setting up scales and adding axis.

Here is the source of the final graph.

Margins

It’s convention (and rather useful) in d3 to define your margins as an object.

const margin = {
  left: 50,
  top: 10,
  right: 50,
  bottom: 40
}

I’m making the left and right margin equal in size so the graph area is centered in the image.

Loading Data

You can load data using d3.csv (functions for other formats such as xml, text, dsv, etc also exist) that fetches the data at some url and parses it. You can use d3.autoType to automatically parse the data or you can provide your own parsing function.

let data = d3.csv("seattle-temps.csv",
  d => ({
    date: new Date(d.date),
    temperature: Number(d.temp)
  }))

Scales

D3 has built in helper functions to create functions that maps a range of arbitrary values to numbers, used to easily transform datapoints to points in the graph. Here we define the function x() that maps dates within a certain domain to a range of values on the x axis and function y() that maps temperatures to a range of values on the y axis.

let x = d3.scaleTime()
  .domain(d3.extent(data, d => d.date))
  .range([margin.left, width - margin.right])
  .nice()

let y = d3.scaleLinear()
  .domain(d3.extent(data, d => d.temperature))
  .range([height - margin.bottom, margin.top])
  .nice()

The d3.extent function returns an array with the minimum and maximum of the values found in the data. The margins are there to leave some space free for the axis.

Making Axis

You can use these scale functions to aid in the creation of axis for your graph. The d3.axisLeft (and others) function is used to create a helper function that can create a bunch of loose elements that together form an axis for our graph. We append a <g> group element first so these objects are grouped together and translate this group to place the axis in the right place. We also add a label for the y axis to this group.

svg.append("g")
  .attr("transform", `translate(${margin.left},0)`)
  .call(d3.axisLeft(y))
  .append("text")
  .attr("y", -margin.left + 16)
  .attr("x", (-height + margin.bottom - margin.top) / 2)
  .attr("text-anchor", "middle")
  .attr("transform", "rotate(-90)")
  .style("font-size", "16px")
  .style("fill", "currentColor")
  .text("Farenheit");

The x axis is handled similarly but we also set a custom tickFormat function so the months on the y axis are abbreviated.

svg.append("g")
  .attr("transform", `translate(0,${height - margin.bottom})`)
  .call(d3.axisBottom(x).tickFormat(d3.timeFormat("%b")))
  .append("text")
  .attr("x", (width + margin.left - margin.right) / 2)
  .attr("y", margin.bottom - 4)
  .attr("text-anchor", "middle")
  .style("font-size", "16px")
  .style("fill", "currentColor")
  .text("Date");

So far we have an empty graph with some nice axis, see fig. 1.

JanFebMarAprMayJunJulAugSepOctNovDecJanx label35404550556065707580y label

Fallback image

You can create a fallback image by rendering the graph server-side, see D3 & Node, and inserting it into a document. Say this is the resulting html:

<figure>
  <img id="fig:graph" src="graph.svg"/>
  <figcaption>A graph</figcaption>
</figure>

You could then use the following code to replace the img tag with an empty svg tag and render it.

let svg = d3.select("#fig\\:graph")
  .select(function() {
    return this.parentNode
  })
  .insert("svg", "figcaption")

d3.select("#fig\\:graph").remove()

The advantage being that an image is shown immediately, one that would be shown even if the browser doesn’t support javascript or blocks it.