Interactive Applications With React And D3.js

Interactive Applications With React And D3.js

A short tutorial on building interactive applications using React and D3.js

Introduction

D3 stands for data-driven documents. It’s a brand name, but also a class of applications that have been offered on the web in one form or another for years. For quite some time we’ve been building and working with data-driven documents such as interactive dashboards, rich internet applications, and dynamically driven content. In one sense, the D3.js library is an iterative step in a chain of technologies used for data-driven documents, but in another sense, it’s a radical step.

In this article, I’m going to walk you through the basics of D3.js.

Visual Representations

Unlike Processing, Raphael or Protovis', D3’s vocabulary of graphical marks comes directly from web standards like HTML, SVG, and CSS, because of which D3 does not introduce new visual representations. Charts, in D3, are just rectangles with shapes inside of them; there are a handful of configurations of shapes we recognize as common charts or graphs. Instead, the package provides a way to define your own visual representations by manipulating graphical marks or creating your own shapes which also makes it easy to add interactions to visuals and declare how they behave.

On The Web

One of the reasons to use D3.js is that we can share our work instantly with anyone who uses a web browser (which makes it at least half of the people on Earth!). That means you need to have at least a passing understanding of the medium you’ll be working in, which is HTML5 along with basics of SVG, HMTL and CSS for calling APIs.

Getting Started With D3.js

How do you build up your visualization using D3.js' principles? This can be done piece by piece with a whole lot of utility functions that are available with the software. As you’ve probably seen, D3.js’ API is massive.

Let's explore a list of utilities that are available on D3.js:

d3-scale

Scales are a convenient abstraction for fundamental tasks during the visualization process using which you can map a dimension of abstract data to a visual representation. Although most often used for position-encoding quantitative data, i.e. mapping a measurement in meters to a position in pixels for dots in a scatterplot, scales can represent virtually any visual encoding, such as diverging colors, stroke widths or symbol size. They can also be used with almost any type of data, such as named categorical data or discrete data which require sensible breaks. An example of this is given below for your understanding:

var x = d3.scaleLinear()
    .domain([10, 130])
    .range([0, 960]);

x(20); // 80
x(50); // 320

You can apply a color encoding using the snippet given below:

var color = d3.scaleLinear()
    .domain([10, 100])
    .range(["brown", "steelblue"]);

color(20); // "#9a3439"
color(50); // "#7b5167"

d3-selection

Selections allow powerful data-driven transformation of the document object model (DOM) using set attributes, styles, properties, HTML, text content and more. Using the data join’s enter and exit selections, you can also add or remove elements to correspond to data. Selection methods typically returns the current selection or a new one to allow the concise application of multiple operations on a given selection via method chaining.

Traditional D3 rendering with React

The challenge of integrating D3 with React is that both will attempt to control the DOM as the select/enter/exit/update patterns on D3 are in direct conflict with React and its virtual DOM. If you’re coming to React from D3, giving up your grip on the DOM is one of those “dead hands” moments. Most people use D3 with React to build the structure of an application by rendering traditional HTML elements. Coming to the data visualization section, a DOM container (typically an <svg>) is passed over to D3 which uses it to create, destroy and update elements. The benefit of integrating React and D3 is that you can use the same kind of code you see in all the core D3 examples, hence offering familiarity. On the other hand, the main difficulty that arises from this method is that you need to create functions in various lifecycle events to ensure that your visualisation is updated on React.

Use the code given below:

d3.selectAll("p")
    .attr("class", "graf")
    .style("color", "red");

d3-array

Data in JavaScript is often represented by an iterable (such as an array, set or generator) because of which iterable manipulation is a common task when analyzing or visualizing data. You can filter an array using a predicate function or map it to a parallel set of values using a transform function using the methods that the array provides. Refer to the code given below:

const m = d3.min(array);

d3-format

Rounding errors are not the only reason to customize number formatting; a table of numbers should be formatted consistently for comparison. It is also important to ensure that large numbers have grouped digits (e.g., 42,000) or have a scientific or metric notation (4.2e+4, 42k) when using this method, while currencies should have fixed precisions ($3.50). To go forward, the reported numerical results should be rounded to significant digits (4021 becomes 4000) and number formats should be appropriated to the reader’s locale (42.000,00 or 42,000.00). The list goes on...

Formatting numbers for human consumption is the purpose of d3-format but this feature is much more than an alias for number.toFixed! A few more examples are given below:

d3.format(".0%")(0.123);  // rounded percentage, "12%"
d3.format("($.2f")(-3.5); // localized fixed-point currency, "(£3.50)"
d3.format("+20")(42);     // space-filled and signed, "                 +42"
d3.format(".^20")(42);    // dot-filled and centered, ".........42........."

d3-axis

The axis component of D3.js renders human-readable reference marks for scales which in turn alleviates one of the more tedious tasks when visualizing data. An axis consists of the path element of class domain representing the extent of the scale’s domain, followed by the transformed elements of tick, which represents each of the scale’s ticks. Each tick has a line element to draw the tick line and a text element for the tick label. For example, here is a typical bottom-oriented axis:

d3.select("body").append("svg")
    .attr("width", 1440)
    .attr("height", 30)
  .append("g")
    .attr("transform", "translate(0,30)")
    .call(axis);

The listing below shows a simple bar chart component built using this method. Create this component in your src/ directory and save it asChart.js:

import React,{useEffect, useRef} from "react"
import { scaleLinear, scaleBand } from 'd3-scale';
import { max } from 'd3-array';
import {format} from 'd3-format';
import {axisBottom, axisLeft} from 'd3-axis'
import { select } from 'd3-selection';

const width = 960;
const height = 500;
const margin = {top: 20, right: 40, bottom: 20, left: 80};
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;

const Chart = ({data}) => {
   const svg = useRef(null);
   useEffect(() => {
     renderBar(data)
   }, [data])
   const renderBar = (data) => {

      const xValue = d => d.population;
      const yValue = d => d.country;

      const xScale = scaleLinear()
      .domain([0, max(data, xValue)])
      .range([0, innerWidth]);

      const yScale = scaleBand()
      .domain(data.map(yValue))
      .range([0, innerHeight])
      .padding(0.1);

      const g = select(svg.current).append('g')
      .attr('transform',`translate(${margin.left}, ${margin.top})` );

      const xAxisTickFormat = number =>
        format('.3s')(number)
        .replace('G', 'B');

      const xAxis = axisBottom(xScale)
       .tickFormat(xAxisTickFormat);

      g.append('g').call(axisLeft(yScale));
      g.append('g').call(xAxis)
      .attr('transform', `translate(0,${innerHeight})`);

      g
      .selectAll('rect')
      .data(data)
      .enter()
      .append('rect')
      .attr('y', d => yScale(yValue(d)))
      .attr('width', d => xScale(xValue(d)))
      .attr('height', yScale.bandwidth())
      .attr('rx', 3)
   }
   return <svg  height={height} width={width} ref={svg} style={{fill: 'steelblue'}} />

}

export default Chart

A few explanations about what’s going on in the code here:

  • We’re importing D3 functions from modules which don’t have the D3 prefix as they’re imported function as in the case of scaleLinear.

  • Data is passed down as props to the component which makes it possible to access it using this.props.data.

  • Making these changes and saving it won’t show any immediate effect because you’re not importing and rendering these components in App.js, which is the component initially rendered by your app. Change App.js to match the following listing:

import Chart from './Chart'
import './App.css';

function App() {
 const data = [
   {
     country: "China",
     population: 1394015977
   },{
country: "India",
     population: 1326093247
   },
   {
     country: "United States",
     population: 329877505
   },
   {
     country: "Indonesia",
     population: 267026366
   },
   {
     country: "Pakistan",
     population: 233500636
   }
 ]
 return (
   <div className="App">
       <h3>D3 EXAMPLE</h3>
     <Chart data={data}/>
   </div>
 );
}

export default App;
  • When you save App.js with these changes, you’ll see something pretty cool if you have your server running as the server automatically updates the page to show you what’s in the figure below. That’s Webpack — the module bundler included in create-react-app — automatically updating your app based on changes in your code. Here is a pictorial representation for your understanding:

Screenshot 2021-06-20 at 11.53.25 AM.png

Summary

You’ve had an overview of D3 with a focus on how well suited it is for developers building web applications for the modern browser. I’ve highlighted the standardizations and advances that allow this to happen:

  • A example of data visualization you can create with D3.

  • A first look at selection to create and change elements on the page.

  • An overview of the DOM, and SVG.

D3.js is another JavaScript library, one of thousands, but it’s also indicative of a change in our expectations of what a web page can do. Although you may initially use it to build one-off data visualizations, D3 has much more power and functionality than that. We’ll explore the ways that you can use D3 to create rich, data-driven documents that will enthrall and impress in later articles.

Hope you had a great read!