Learning D3.js

2021, Jan 16    

D3.js is a popular and powerful data visualisation js framework which helps developers and business stakeholders to visualise and demonstrate ideas via representing big chuncks of data. Compare to other tools, it provides the following advantages:

  1. popular and well-known: it contains lots of resources and tutorials make the learning process relatively easy.

  2. flexibility: the framework itself comes with greate flexibility from low level components like shapes, scales, animations to high-level from gadgets.

  3. renowned for animation and interaction, animations can be a powerful device for storytelling.

Example of embededd D3.js cells

1. Making it more effective

The Overservable provides an ideal environmment for learning D3, which based on the input provides feedback with D3.js render feedback during runtime. Refer to the introduction about observables

  • Tips for Observables:
    • you can import others observable libraraies/modules in your own observable files
    • observables have its own way of organising codes, no necessary top down.
    • you can insert cells as views.
    • you can insert cells as markdowns.
    • you need curly brackets to insert a code blocks
    • cells implicity await promises.
  • Observables can be embedded into html pages or via javascripts, supports specify hot version of the embeded links or specific versions for it. In the same time, the container scrpts can also access data from it.

  • Styling your observable via tachyon

2. References

2.1 API

d3js_module

D3.js provides a comprehensive list of modules, each module serves its unique use cases. Some of them are UI related modules which produces SVG elements inside the dom and the rest is data layer modules provide util classes and functions for data processing. Some of the important modules I felt useful for entry level as been listed here:

Module Name Description Type
Array, Interpolator, Scales Provides basic static operations, search, matrix transformation, set operations, etc. the data can also be later used to further transformed to be suited into histograms and axes. Data
Axes Create human readable reference marks for scales, example, users can choose tick generators for generating ticks or manually specify ticks UI
Brush Brushing is a interactive specification region using to respond users mouse clicking or dragging on the diagram UI
Colors and Color Schemes Provides necessary methods for color modification, editing and also come with a set of default color scehemes Data
Drag for drag and drop detection and Timer, Transition, Easing, Zoom for smoother animation For enabling drag and drop effects as well as providing smoother animations UI
d3-dsv & Fetch Using the dsv module to measure, filter, preprocess the data source and using the Fetch api to download the data source Data
Force Force-directed graph layout using velocity Verlet integration, this is a very well know feature provided by d3, you can see tones of examples in Observable UI
Plogons, Paths, Shapes provide geometric operations towards on polygons and paths and varies shapes UI
Selections Transform the DOM by selecting elements and joining to data. supports create, modify, filter, merge and various operations UI

Other interesting and good to have modules includes: d3-chord, [d3-contour][https://github.com/d3/d3/blob/master/API.md#contours-d3-contour], number formats, geo-graphics, quad-tree

2.2 Intro

Nice tutorial covers important modules in section 3.2, mainly about selection, shape, transition, scales and forces.

2.3 Introduction between D3 and canvas

d3.js is rendering based on SVG, but under cases where complex graph structure is presented. There are also ways the data can be rendered via canvas(refer to the link above)

2.4 Types of charts

When talking about data visualisation, it will be better to use the common termonology widedly accepeted by the industry. So others will have a clear idea on what kind of layout and design you are describing. The following links provide pretty comprehensive list of types of chars people usually use.

3. Case Study

3.1 Observable - Brush on Dot Plotting

In the first case study. it demonstrates how to use simple blocks of source code to generate a randomly plotted dots across horizontal axis and provide a responsive draggable brush. The out come of the chart is displayed as follows:

The code is explained in the following block.

/** import */
d3 = require("d3@6")

/** layout constant */
height = 120
margin = ({top: 10, right: 20, bottom: 20, left: 20})

// scale from with - margin, cut into 10 pices and return the array
x = d3.scaleLinear([0, 10], [margin.left, width - margin.right])

// function: upshift the line and create xAxis
xAxis = g => g
    .attr("transform", `translate(0,${height - margin.bottom})`)
    .call(d3.axisBottom(x))

// rx & ry functions
rx = d3.randomUniform(...x.domain())
ry = d3.randomNormal(height / 2, height / 12)

// chart plotting
chart = {
  const svg = d3.create("svg")
      .attr("viewBox", [0, 0, width, height]);

	// specify the entire brush region and initialize the brush 
  const brush = d3.brushX()
      .extent([[margin.left, margin.top], [width - margin.right, height - margin.bottom]])
      .on("start brush end", brushed);

	// plot out the circle, using join to plot out all the dot data at once.
	// refer to selection.join function: 
	// https://github.com/d3/d3-selection/blob/master/README.md#selection_join
  const circle = svg.append("g")
      .attr("fill-opacity", 0.2)
    .selectAll("circle")
    .data(Float64Array.from({length: 800}, rx))
    .join("circle")
      .attr("transform", d => `translate(${x(d)},${ry()})`)
      .attr("r", 3.5);

  // append X axis.
  svg.append("g")
      .call(xAxis);

	// set initial move ment
  svg.append("g")
      .call(brush)
      .call(brush.move, [3, 5].map(x))
      .call(g => g.select(".overlay")
          .datum({type: "selection"})
          .on("mousedown touchstart", beforebrushstarted));

  // handling brush operation
  function beforebrushstarted(event) {
    const dx = x(1) - x(0); // Use a fixed width when recentering.
    const [[cx]] = d3.pointers(event);
    const [x0, x1] = [cx - dx / 2, cx + dx / 2];
    const [X0, X1] = x.range();
    d3.select(this.parentNode)
        .call(brush.move, x1 > X1 ? [X1 - dx, X1] 
            : x0 < X0 ? [X0, X0 + dx] 
            : [x0, x1]);
  }

  function brushed(event) {
    const selection = event.selection;
    if (selection === null) {
      circle.attr("stroke", null);
    } else {
      const [x0, x1] = selection.map(x.invert);
      circle.attr("stroke", d => x0 <= d && d <= x1 ? "red" : null);
    }
  }

  return svg.node();
}

3.2 Observable - Table Repo

This repo is a hand crafted table visualisation component using Observable. It is nice that I can fork it out and edit all the way and observe how each component works.

TOC