Mitchell Hillman

Simple chart using D3.js

This post outlines the basics of how to use the d3 javascript library to make a simple chart.

Contents:

Finished Example

First, import the d3 library into your project. This example will use version 7.6.1 of the d3 library hosted from a cdn.

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.6.1/d3.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

SVG dimensions

Create an SVG element and set its size.

<svg id="my-chart"></svg>
const element = document.getElementById("my-chart");

const width = 400;
const height = 200;
const padding = {
  bottom: 50,
  left: 65,
  right: 0,
  top: 10,
};

const svg = d3
  .select(element)
  .attr("width", width + padding.left + padding.right)
  .attr("height", height + padding.top + padding.bottom);

Define X and Y Axis

Set the range and domain of the x and y axis of your data using one of the d3 scale types.

const xScale = d3
  // d3.scaleBand() requires an actual Date object to work properly.
  .scaleBand()
  .domain(data.map(({ date }) => new Date(date)))
  .range([0, width]);

const yScale = d3
  .scaleLinear()
  // d3.max function is helpful for determining the maximum value from a data set.
  .domain([0, d3.max(data.map(({ amount }) => amount))])
  .range([height, 0]);

domain is the total amount of data this chart will display on that axis. range is the physical space in pixels that amount of data will be spread across. Take note that width is the second element of the array for the yScale, but height is the first element for the xScale. Depending on your design those may need to be reversed.

Draw the axis labels

Create a g group that will position all the future graphic elements

const graph = svg
  .append("g")
  .attr("id", "graph")
  .attr("transform", `translate(${padding.left}, ${padding.top})`);

Use d3.axisLeft and d3.axisRight respectively to make an axis generator based on the scales just created

const axisLeftGenerator = d3
  .axisLeft(yScale)
  .tickFormat(d3.format(".1s"))
  .ticks(5)
  .tickSize(0);

const axisBottomGenerator = d3
  .axisBottom(xScale)
  .tickSize(0)
  .tickFormat(d3.timeFormat("%b"));

Append the left axis to the graph and configure the text and general appearance

const axisLeft = graph
  .append("g")
  .attr("id", "axisLeft")
  .call(axisLeftGenerator);

axisLeft.selectAll(".tick text").attr("transform", "translate(-11, 0)");

axisLeft
  .selectAll(".tick")
  .attr("style", "font-size: 12px; text-transform: uppercase")
  .append("rect")
  .attr("fill", gridColor)
  .attr("width", width)
  .attr("height", 1);

Do the same for the bottom axis.

graph
  .append("g")
  .attr("id", "axisBottom")
  .attr("transform", `translate(0, ${height})`)
  .call(axisBottomGenerator)
  .selectAll(".tick text")
  .attr("transform", "translate(0 , 13)");

In this example some styles apply to both x and y axis.

graph.selectAll(".domain").attr("stroke", gridColor);

graph
  .selectAll(".tick text")
  .attr("fill", textColor)
  .attr("style", "font-size: 12px;");

Draw dots for each point

graph
  .append("g")
  .attr("fill", blue)
  .attr("id", "dots")
  .selectAll("circle")
  .data(data)
  .enter()
  .append("circle")
  .attr("r", 5)
  .attr("transform", ({ amount, date }) => {
    const x = xScale(new Date(date)) + xScale.bandwidth() / 2;
    const y = yScale(amount);
    return `translate(${x}, ${y})`;
  });

Draw a line connecting the dots

graph
  .append("path")
  .attr("id", "my-path")
  .datum(data)
  .attr("fill", "none")
  .attr("stroke", blue)
  .attr("stroke-width", 1)
  .attr(
    "d",
    d3
      .line()
      .x(({ date }) => xScale(new Date(date)) + xScale.bandwidth() / 2)
      .y(({ amount }) => yScale(amount))
  );