Manipulating SVG using D3.js library

Posted by Antonio Grudiček Friday, Jan 19, 2018
D3.js logo (Screenshot Source: https://bl.ocks.org/mbostock/e787e283b8cf9d5a8f8a)

If you are a frontend developer working with SVG images, you probably got in touch with various JavaScript libraries made specifically for manipulating vector images, like Raphael, SnapSVG or SVG.js. Although it’s not limited only to vector images, a great alternative to aforementioned libraries is D3.js.

D3.js or only D3 (Data Driven Documents) is a JavaScript library used for manipulating documents based on data (data-driven approach to DOM manipulation) and for making dynamic and interactive data visualizations. It works great with commonly used standards like HTML, CSS and SVG. Manipulating SVG with D3 isn’t different than manipulating any other DOM element (ok, we still should take care that there are some differences between HTML and SVG elements, e.g. in some CSS properties like background vs fill or border vs stroke, etc.).

The first version of D3 was released in 2011, and a library is still being actively developed. The latest release is 4.12.0, released on Nov 21, 2017 (as of Dec 21, 2017). D3 became very popular because of its great documentation, a huge number of examples and active community that constantly implements new functionalities and bug fixes. The library is licensed under the BSD 3-clause License.

D3 homepage: https://d3js.org/
GitHub page: https://github.com/d3/d3

D3 API looks similar to jQuery and is exposed through the d3 global variable. To use this variable, a library must be included in our application using the script tag. We can either include the whole library or only needed, independent modules like d3-selection, d3-scale, d3-axis etc (from the v4.0.0).

D3 uses a declarative approach in DOM elements selection and works on sets of nodes called selections. Each selection is an array of nodes. D3 uses selectors defined by W3C Selectors API. Desired elements can be selected by element name, id, class, attribute, content and other criteria.

Selecting SVG elements using D3:

// select all SVG path elements in document order
d3.selectAll("path");

// select only the first SVG path element
d3.select("path");

// select SVG path elements with class "land"
d3.selectAll("path.land");

// select SVG path element with id "US"
d3.select("path#US");

// selections can also be chained
d3.select("svg").selectAll("circle");

Once DOM elements are selected, the library provides API for modifying them: adding and removing elements, modifying elements (changing element’s text and HTML content, adding, removing and changing attributes, setting element styles), registering event listeners, applying animations and transitions on selected elements etc.

Adding and removing SVG elements:

// Add (append) blue SVG circle, set radius to 25 and circle center to (25, 25)
d3.select("svg").append("circle").attr("r", 25).attr("cx", 25).attr("cy", 25).style("fill", "blue");

// Remove all SVG circles
d3.select("svg").selectAll("circle").remove();

Setting SVG element styles, attributes and content:

// set fill of all SVG rect elements to red
d3.selectAll("rect").style("fill", "red");

// set radius attribute of all SVG circle elements to 50
d3.selectAll("circle").attr("r", 50);

// set text content of all SVG tspan elements to “new text”
d3.selectAll("tspan").text("new text");

// set html content of all SVG tspan elements to the link to google homepage
d3.selectAll("tspan").html("https://www.google.hr/");

// add class "new-class" to DOM element
d3.select("tspan").classed("new-class", true);

// remove class "new-class" from DOM element
d3.select("tspan").classed("new-class", false);

// add class "new-class" to DOM element conditionally
d3.select("tspan").classed("new-class", function() {
    return Math.random() >== 0.5;
});

Registering and removing event listeners:

// register click event handler on all SVG path elements
d3.selectAll("path").on("click", function() {
   console.log("Path element clicked");
});

// remove click event handlers from SVG path elements
d3.selectAll("path").on("click", null);

D3 transitions are used to animate transitions over time from a current state to an end state. This can be achieved by using D3 selection’s transition method. This method allows us to define properties that will be changed, how these properties will be changed, time properties like transition duration, delay and easing and what the end state will look like.

D3 transition examples:

// change circle’s fill from green to red
d3.select("circle").style("fill", "green").transition().style("fill", "red");

// change circle’s fill from green to red during 1000ms with 500ms delay
d3.select("circle").style("fill", "green").transition().delay(500).duration(1000).style("fill", "red");

// transitions chaining - move circle horizontally for 100px during 1000ms, wait 2000ms, then move circle vertically for 100px during 1000ms
d3.select("circle").transition().duration(1000).attr("cx", 100).transition().delay(2000).duration(1000).attr("cy", 100);

// aforementioned transition chaining with easing
d3.selectAll("circle").transition().duration(1000).ease(d3.easeBounce).attr("cy", 100).transition().delay(5000).duration(1000).ease(d3.easeCircle).attr("cx", 100);

// start transition on click (other events can be used too)
d3.select("circle").on("click", function () {
    d3.select(this).transition().duration(1000).attr("cx", 100);
});

Probably the best feature that D3 library provides is binding data to DOM elements. For example, it’s possible to create elements and specify element content, attribute values, styles, transitions, dynamic interactions and other properties dynamically, depending on bound data, using the functions of data (useful when creating charts and tables from the array of numbers).

D3 uses a declarative style of programming to bind data to DOM elements by providing a data method that joins an array of data with the elements selection (binds data to selection) and returns three virtual selections:

  • enter
  • update
  • exit

These virtual selections can be represented by the following image:

Data values joined to existing elements produce the update selection - in the middle. Unbound data on the left side produces the enter selection - represents missing elements. On the other side, remaining unbound elements produce the exit selection - represents elements to be removed.

Virtual selection enter contains placeholders for missing DOM elements our data will be bound to because real elements don’t exist yet. This selection is used when the number of DOM elements is less than the data length. After that, we can append or insert (using D3’s append or insert methods) DOM element for every placeholder element returned by enter method.

The following example will append new SVG circle element for every value in the data array. Element’s center coordinates and radius are set depending on the bound data using the functions of data.

var dataArray = [10, 20, 30];

var circles = d3.select("svg")
    .selectAll("circle")
    .data(dataArray);

// DOM elements don’t exist yet, so we have to use enter selection
// and append new elements depending on dataset
circles.enter()
    .append("circle")
    .attr("cx", function (d) {
        return d;
    })
    .attr("cy", function (d) {
        return d;
    })
    .attr("r", function (d) {
        return d;
    })
    .style("fill", function() {
        return "hsl(" + Math.random() * 360 + ",100%,50%)";
    });

If we want to update SVG elements created before and already bound to data, we will use the update selection. This will only update existing DOM elements with the new data and no elements will be added or removed. Update selection is used when the number of elements is the same as data length.

Let’s say we want to re-position previously created elements and change their radius:

var dataArray = [15, 25, 35];

var circles = d3.select("svg")
    .selectAll("circle")
    .data(dataArray);

// DOM elements exist already, so we have to use update selection
// and update elements with new data
circles.attr("cx", function (d) {
        return d;
    })
    .attr("cy", function (d) {
        return d;
    })
    .attr("r", function (d) {
        return d;
    });

If a specific value is removed from the data array, we want to remove unbound elements too. This can be achieved using the exit selection that contains elements to be removed. Exit selection is used when dataset contains fewer items than there are available DOM elements.

Such elements can then be removed using D3’s remove method:

var dataArray = [25, 35];

var circles = d3.select("svg")
    .selectAll("circle")
    .data(dataArray);

// DOM elements exist already but the number of elements is less than
// dataset length, so we have to use exit selection and remove unbound
// elements
circles.exit().remove();

D3 is a great library and has a lot to offer. Its emphasis on web standards and data binding feature makes it a good choice if you want to bring your data to life and make powerful data visualizations.
My goal here was to give a brief introduction and cover some core concepts, but there’s much more to learn about D3. Hopefully, this article has given you a good starting point. There’s a lot of tutorials and resources for learning D3 and investigation of what is it possible to do with D3, first of all, D3 Wiki and API Reference, as well as D3 Gallery containing many examples. Good luck!

comments powered by Disqus

news / events / blogs