Starting with D3js like a pro

By Liron Hazan, Senior Frontend Engineer at SentinelOne

source: d3 official gallery

D3 is one of the most popular libraries for data visualisation, and learning its modules (APIs) is extremely powerful when you aim to create beautiful custom infographics.

D3 stands for Data Driven Development as the name applies, by using it we visually express our most important thing: data.

We do so by manipulating DOM HTML/SVG group of elements, but we can also select just one Canvas element and draw performant graphics directly using the Canvas API (This way we’ll prevent browser layout calculations that could get expensive if we have hundreds or more elements to draw and maybe even run it offscreen because we’re performance nerds).

source by Naor Suki + Liron Hazan tweaks

Learning D3 can be overwhelming but really rewarding, so in this post I gathered some informative guidelines to get you started easily.

D3 manipulates the data we pass into it, using several layout related algorithms it calculates the (x, y) coordinates of each shape/element we intent to draw.

The browser rendering engine is responsible for the actual drawing — in many cases we’ll probably use SVG, but in some cases we may use Canvas. It’s important to be familiar with each API (at least on a basic level) so that we’ll understand the generated elements role and behaviour (in case of SVG) and for creating efficient infographics.

Create a “draw area” as the main selected SVG element. declare a group (‘g’ attribute) appended element that represent your graphic. Doing so decouples selections and will ease the use when appending other ‘g’ graphics to the draw area.

const svgDrawer = select('#tree-container')
.attr('width', width)
.attr('height', height)
.attr('transform', `translate(${margin.left} ,${})`);

// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
const firstGroupSelection = svg.append('g');...

The “margin” convention by Mike Bostock, the creator of D3, is a technique which used to move the svg group element on the screen 📺. You’ll want to use it for locating your group where needed (when building a Bar chart for e.g. to reveal the x/y axis).

const margin = { top: 20, right: 90, bottom: 30, left: 50};....const svgDrawer = select('#tree-container')
.attr('width', width)
.attr('height', height)
.attr('transform', `translate(${margin.left},${})`);

We set the margin props on a margin object. By adding the ‘transform’ attribute to the SVG container, we’re moving the container graphics by 20px from the top and 50px left.

It’s important to understand what happens under the hood when we select an element or group of elements, what is a Selection?

In short, its an object which holds references to the selected elements and can operate on them using methods it exposes ( e.g. attr(), style(), classed(), each(), node(), clone() etc..).

My following post could give you a deep perspective on that topic:

D3 Selections in Depth

Data binding (generally updates) is a core concept of D3.

Data binding means to update the UI according to the current data.

When learning D3 you’ll probably face the datum() and data() operations and may get confused-

  • Datum: Used for static/one time binding the data directly to the element/s (no use of join).
  • Data: Joins the data array to the selected elements (data binding) and returns an updated selection (the elements successfully bound to data).

In most cases we’ll use the data() rather than datum() and the update is only relevant to that.

I recommend reading this notebook (by Mike Bostok the creator of d3).

As written in the notebook: using join() specifies exactly what happens to the DOM as data changes:

Joining data generates selectors of “enter”, “exit” and “groups”.

You can play with the following example of just enter():

Bar Chart- no axis

In prior versions to d3-selection 1.4, we used the general update pattern: data() → enter() → merge() → exit(). Meaning we implemented each function per each type selector (enterSelector, exitSelector..). But this pattern wasn’t easy to catch and easy to forget (as mentioned in the notebook), so it’s deprecated.

Today we’re using the join method: by join(‘selector’) OR join(enter => enter … , update => update.. , exit => exit ….) and we can specify our enter, update and exit functions. So you can understand that under the hood the “old” enter(), merge() exit() are still called.

Scales are a convenient abstraction for a fundamental task in visualization: mapping a dimension of abstract data to a visual representation.

In simple words: using the scale API we can map the data (data domain) to space on the screen (pixels, output range) or to a certain color range (by order).

There are several types of scales, getting to know them (not too deeply) will surely help you in picking the right one when needed.

There’s another core part of d3 which is algorithms.

Part of the magic in d3 is that you don’t have to know the algorithms and can still make a beautiful infographics.

IMO getting some familiarity with the algorithms, even if not understanding them entirely, will definitely get you to the next level of using d3. I don’t have a rich background in mathematics but for visual algorithms I usually look for a short youtube video just to grasp the concept.

There’s an interesting post by Mike Bostock called “visualising algorithms” I recommend reading.

The SentinelOne attack storyline

In SentinelOne, one of our uses with D3 was in making an interactive way of investigating processes running on a machine 💻 . We give the user the ability of process walking (navigation) forwards or backwards in time (d3-scales!). The user can zoom in/out the tree (d3-zoom) and mark the way from a selected process to the root initiator, identify/ mark malicious processes from none-malicious ones and more.

More not covered

The D3 lib has a lot more to offer, e.g. transition animation for selections, hierarchy layouts, force layout, dragging API, etc etc..

In this link you can find the entire D3 API features we overviewed and many more generally useful utils you can use as well such as:

  • Time format functions: e.g. d3.timeParse(‘%y%m%d’).
  • Fetch API helpers: e.g. d3.json() for loading a JSON file.
  • Transformations: d3.range — generate a range of numeric values.
  • Transformations: d3.shuffle — randomize the order of an array.

You can also explore the modules visually by following the “collection” filter in observebalehq or by Amelia Wattenberger beautiful blog. check it out if you haven’t!!


Summing up

The more I use D3 the more I love it, its functional style of writing, the agility it gives you as a developer when it comes for customisations, the split to independent modules (single responsibility), the motivation it gives me for exploring other fields as algorithms that are used for data vis, and the fact it keeps me close to the DOM ❤

Thanks Oz Gonen for reviewing the post and for a contagious pixel perfect sight!!

Cheers, Liron.

This is the tech blog of Sentinelone, a leading cybersecurity company. Follow us to learn about the awesome tech we build here.