Replacing jQuery with D3

[tl;dr] You can easily replace jQuery in your visualization projects by using D3-only functions.

When creating visualizations or interactives we often use a combination of jQuery and D3.

Mostly we use many of D3’s functions and only a small subset of jQuery’s for DOM-manipulation.

View Project on Github

Although D3 has powerful features like its selectors or an ajax wrapper already built-in, we are often times missing some jQuery functions in our D3 projects. That’s why we will show some approaches on how you can replace jQuery by only using D3. The main benefits of this are:

  • Reduced overhead in your visualization projects by removing unused functions.
  • Only loading a single library (smaller size).
  • Not mixing up libraries that are built for different use-cases.
  • Using functions in a D3-like way.

To start off, let’s have a look at the similarities of D3 and jQuery. If you are new to D3 and have knowledge in jQuery, you might want to use this as a starting point as well. After that we will look at which jQuery functions you can replace with D3 and how that is done.



Similarities

Selectors

Both of the libraries are based on sophisticated selector functions that are easy to use.

jQuery
$('.foo').addClass('foobar');
$('.foo').removeClass('foobar');
D3
d3.selectAll('.foo').classed('foobar', true);  
d3.selectAll('.foo').classed('foobar', false);

Attribute and style manipulation

With the two libraries, you can make manipulations to the CSS and attributes of DOM elements.

jQuery
$('.foo').attr('data-type', 'foobar');
$('.foo').css('background', '#F00');
D3
d3.selectAll('.foo').attr('data-type', 'foobar');  
d3.selectAll('.foo').style('background', '#F00');

Ajax

Although the syntax is a bit different, D3 and jQuery have handy ajax wrappers for different data types.

jQuery
$.getJSON('http://url-to-resource.json', doSomething);
$.ajax({
    url: 'http://url-to-resource.txt',
    dataType: 'text',
    type: 'GET',
    success: doSomething
});
D3
d3.json('http://url-to-resource.json', doSomething);  
d3.text('http://url-to-resource.txt', doSomething);

Manipulating classes

Often times, one needs to manipulate classes of DOM elements, for example to toggle CSS styles.

jQuery
$('.foo').addClass('foobar');
$('.foo').removeClass('foobar');
D3
d3.selectAll('.foo').classed('foobar', true);  
d3.selectAll('.foo').classed('foobar', false);

Append and Prepend

Inserting child nodes is an important feature, especially when generating visualizations from input data. You can do this task easily with jQuery and D3.

jQuery
$('.foo').append('<div/>');
$('.foo').prepend('<div/>');
D3
d3.selectAll('.foo').append('div');  
d3.selectAll('.foo').insert('div');

Listening to events

The same syntax is used in D3 and jQuery to listen to events on the selected elements. You can listen to the click event like this:

jQuery
$('.foo').on('click', clickHandler);
D3
d3.selectAll('.foo').on('click', clickHandler);

Removing elements

Sometimes you need to remove elements from the DOM. D3 and jQuery implement this function like this:

jQuery
$('.foo').remove();
D3
d3.selectAll('.foo').remove();

Subselecting elements

With these function you can select child elements from a larger selection.

jQuery
$('.foo').find('.bar');
D3
d3.selectAll('.foo').selectAll('.bar');

Content manipulation

If you want to change the contents of a DOM node, you can use the functions below in jQuery and D3.

jQuery
$('.foo').text('Hello World!');
$('.foo').html('<div class="bar">Hello</div>');
D3
d3.selectAll('.foo').text('Hello World!');  
d3.selectAll('.foo').html('<div class="bar">Hello</div>');

Replacements

Now we look at the functions that are implemented in jQuery but missing in D3. For all functions listed here, we have given a simple solution that can be used in one place and a more generic implementation that enables you to use these functions everywhere in your app and with D3’s method chaining.

trigger events and custom events

One cool feature of jQuery is how it works with events. You can easily fire and listen to custom events for any element on the page. For example you could fire a custom event with some new data on your document and listen to this event in the code:

//event listener
$(document).on('dataChange', function(evt, data) {
    //do something with evt and data
    console.log(data.newData);
});

//trigger the event
$(document).trigger('dataChange', {
    newData: 'Hello World!'
});

In D3, that functionality is not directly supported, but there are ways to do it in a similar way. A simple solution (if you do not need d3.event in your handler):

//event listener
d3.select(document).on('dataChange', function(data) {  
    console.log(d3.event); //null
    console.log(data.newData);
});

//trigger the event
d3.select(document).on('dataChange')({  
    newData: 'Hello World!'
});

A more generic approach is to add the function to the d3 object, so that it can be used on all selections.

d3.selection.prototype.trigger = function(evtName, data) {  
  this.on(evtName)(data);
}

Adding this function to D3 enables you to get a jQuery-like trigger function for events that can be used like this:

d3.select(document).on('dataChange', function(data) {  
  console.log(data);
});

d3.select(document).trigger('dataChange', {newData: 'HelloWorld!'});

after() and before()

With jQuery we can easily insert elements right after all elements of a selection. Given an example of 3 elements:

<ul>
    <li>List</li>
    <li>List</li>
    <li>List</li>
</ul>

We can now use a simple jQuery function for inserting a new element after each list-item:

$('li').after('<li>Item</li>');

This is how the list looks like after the above function was executed:

<ul>  
    <li>List</li>
    <li>Item</li>
    <li>List</li>
    <li>Item</li>
    <li>List</li>
    <li>Item</li>
</ul>

To do the same thing with D3, you can iterate over all elements in a selection and append elements using plain JavaScript:

d3.selectAll('li').each(function() {  
  var li = document.createElement('li');
  li.textContent = 'Item';
  this.parentNode.insertBefore(li, this.nextSibling);
})

A better approach would be to make this a generic function which adds elements based on the tag name and returns a new selection of the created elements so that they can be edited afterwards:

d3.selection.prototype.after = function(tagName) {  
  var elements = [];

  this.each(function() {
    var element = document.createElement(tagName);
    this.parentNode.insertBefore(element, this.nextSibling);
    elements.push(element);
  });

  return d3.selectAll(elements);
}

If you add this to your D3 code, you can use the after() function similar to the one in jQuery:

d3.selectAll('li')  
    .after('li')
    .text('Item')
    //do something else with the inserted elements...

The before() function looks very similar. The only difference is that elements get inserted before the selection of elements.

d3.selection.prototype.before = function(tagName) {  
  var elements = [];

  this.each(function() {
    var element = document.createElement(tagName);
    this.parentNode.insertBefore(element, this);
    elements.push(element);
  });

  return d3.selectAll(elements);
}

empty()

This is an easy one. jQuery gives you an empty() function on selections, which can be used to remove all children in the selection.

<ul>  
    <li>List-Item</li>
    <li>List-Item</li>
    <li>List-Item</li>
</ul>
$('ul').empty();

Will result in:

<ul></ul>

To achieve the same thing on D3’s selection, you simply have to clear the inner HTML of the selected elements:

d3.selectAll('ul').html('');

D3 is often used to create and manipulate SVG. In that case, the upper code might not work, as innerHTML is not supported on SVG. Therefore, it might be better to not call the html() function but select all children of an element and remove them manually:

d3.selectAll('ul').selectAll('*').remove();

The generic code for this is as simple as the example. Note that I am choosing a different name for the function as in jQuery because D3 already has an empty() function which indicates if an element has children or not.

d3.selection.prototype.clear = function() {  
  this.selectAll('*').remove();
  return this;
}

Now you are able to empty a selection similar to jQuery:

d3.selectAll('#foo').clear();

appendTo()

The appendTo() function in jQuery does something similar to D3’s append() function but it appends the preceding selected elements to another selection. To achieve the same thing on D3 selections, you have to iterate over all elements in both selections and append the elements to each other. If you have multiple targets to append the selection, you have to clone the objects first to create the jQuery-like behaviour. This is what I came up with:

d3.selection.prototype.appendTo = function(selector) {  
  var targets = d3.selectAll(selector),
      targetCount = targets[0].length,
      _this = this,
      clones = [];

  targets.each(function() {
    var currTarget = this;
    _this.each(function() {
      if(targetCount > 1) {
        var clone = this.cloneNode(true);
        currTarget.appendChild(clone);
        clones.push(clone);
      }
      else {
        currTarget.appendChild(this);
      }
    });
  });

  if(targetCount > 1) {
    this.remove();
  }

  return clones.length > 0 ? d3.selectAll(clones) : this;
}

So, with this function you can append multiple elements in your DOM to multiple other elements. This example shows the behaviour of the function.

<div class="target"></div>  
<div class="target"></div>

<div class="foo">some element</div>  
<div class="foo">some other element</div>

Now call appendTo() on all elements that have the class foo to append them to the targets.

d3.selectAll('.foo').appendTo('.target');

This will result in the DOM:

<div class="target">  
    <div class="foo">some element</div>
    <div class="foo">some other element</div>
</div>

<div class="target">  
    <div class="foo">some element</div>
    <div class="foo">some other element</div>
</div>

The function returns the appended elements so you can continue to work with them, for example changing their background color:

d3.selectAll('.foo').appendTo('.target').style('background', '#f00');

length()

Sometimes it is useful to know how many elements are in your current selection. jQuery has a built in property for that called length.

<div class="foo"></div>  
<div class="foo"></div>
$('.foo').length; //2

The same thing using D3 is a one-liner:

d3.selection.prototype.length = function() {  
  return this[0].length;
}

With that code you can use:

d3.selectAll('.foo').length() //2

toggleClass()

As said before, you can use D3’s classed function to do manipulations on class names. Anyway, D3 is missing the function to toggle class names which is often used in jQuery. This is how an implementation could look like:

d3.selection.prototype.toggleClass = function(className) {  
      this.classed(className, !this.classed(className));
      return this;
}

eq()

To filter a selection of multiple elements and just selecting a node at a specific index, we can use the eq() function in jQuery. It is quite easy to implement the same function in D3: You just make a subselection on the elements based on the index and return a newly created D3 selection to make method chaining possible:

d3.selection.prototype.eq = function(index) {  
  return d3.select(this[0][index]);
}

show() / hide() / toggle()

These functions are used to change the visibility of elements on your page. They basically just make changes to the CSS rules of the selected elements. For the toggle() function we first have to look if the element is currently visible or not.

Display a hidden element:

d3.selection.prototype.show = function() {  
  this.style('display', 'initial');
  return this;
}

Hide a visible element:

d3.selection.prototype.hide = function() {  
  this.style('display', 'none');
  return this;
}

Toggle the visibility of an element:

d3.selection.prototype.toggle = function() {  
  var isHidden = this.style('display') == 'none';
  return this.style('display', isHidden ? 'inherit' : 'none');
}

moveToFront(), moveToBack()

These are functions that are often missing in D3 but are not related to jQuery. D3 is often used to create and manipulate SVG. Other than with HTML, the order of SVG elements define their visibility (whereas in HTML we have something like z-index). So we are often missing the functionality of moving an SVG selection to the front/back as it is known from Adobe Illustrator.

To make D3 support this functionality, we extend D3’s selections with these functions which are taken from here:

d3.selection.prototype.moveToFront = function() {  
  return this.each(function(){
    this.parentNode.appendChild(this);
  });
};
d3.selection.prototype.moveToBack = function() {  
    return this.each(function() { 
        var firstChild = this.parentNode.firstChild; 
        if (firstChild) { 
            this.parentNode.insertBefore(this, firstChild); 
        } 
    });
};

The usage is very easy, just select an element from your svg and move it around with the provided functions:

d3.select('svg rect')  
    .moveToFront()
    .moveToBack();

We hope that this might be useful for some projects where the jQuery overhead can be replaced by using these simple replacements in D3.

If you want to use the functions we have explained here in your projects, you can check out our extended version of D3 which you can find on GitHub.

View Project on Github

If we have missed something or made a mistake, please drop a comment so that we can fix it.

comments powered by Disqus