How to replace jQuery with D3 in a project

Original author: Christopher
  • Transfer
When creating visualizations or interactive pages, we often use a combination of jQuery and D3. Moreover, D3 is mainly used, and from jQuery they take a small set of functions for manipulating the DOM.

And although there are powerful features in D3 - selectors and ajax wrappers, we often miss some functions from jQuery. We will show how jQuery can be replaced using D3 everywhere. As a result, your code will be simplified, the volume of the project will decrease, and you will not mix different approaches, but will use the functions as is customary in D3.

First, let's look at how these two libraries converge. This is convenient for those who already know jQuery and want to learn D3.

Similarities


Selectors

Both libraries are based on easy-to-use, but feature-rich selectors.

jQuery

$('.foo').addClass('foobar');
$('.foo').removeClass('foobar');


D3

d3.selectAll('.foo').classed('foobar', true);  
d3.selectAll('.foo').classed('foobar', false); 


Manage styles and attributes

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

The syntax is slightly different, but both D3 and jQuery have good wrappers for
ajax.

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);  


Class management

It is often necessary to manage DOM element classes, for example, to switch styles.

jQuery

$('.foo').addClass('foobar');
$('.foo').removeClass('foobar');


D3

d3.selectAll('.foo').classed('foobar', true);  
d3.selectAll('.foo').classed('foobar', false);  


Append and Prepend

Insertion of child nodes is an important function, especially when visualizing input data. This is easy and simple:

jQuery

$('.foo').append('
'); $('.foo').prepend('
');


D3

d3.selectAll('.foo').append('div');  
d3.selectAll('.foo').insert('div');  


Event tracking

The same syntax is designed to track events on selected elements.

jQuery

$('.foo').on('click', clickHandler);


D3

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


Delete items

Sometimes you need to remove items from the DOM. Here's how to do it:

jQuery

$('.foo').remove();


D3

d3.selectAll('.foo').remove();  


Subset Selection

You can select children from a larger

jQuery selection

$('.foo').find('.bar');


D3

d3.selectAll('.foo').selectAll('.bar');  


Content management

You can use the following functions to modify the contents of a DOM node.

jQuery

$('.foo').text('Hello World!');
$('.foo').html('
Hello
');


D3

d3.selectAll('.foo').text('Hello World!');  
d3.selectAll('.foo').html('
Hello
');


Differences


Now consider the features that are in jQuery but not in D3. For each of them, there is a simple solution to replace it, as well as a more general use case, which can be useful to you anywhere in your application, using branded D3 chains.

Activation of events and custom events (trigger events and custom events)

One of the advantages of jQuery is the convenience of working with events. You can trigger or track custom events for any item on the page. For example, you can start a custom event with some data for your document, and track it in code:

//слушаем
$(document).on('dataChange', function(evt, data) {
    //do something with evt and data
    console.log(data.newData);
});
//включаем событие
$(document).trigger('dataChange', {
    newData: 'Hello World!'
});


In D3, this is not directly supported, but you can always achieve this behavior. A simple option (if you do not need d3.event in the handler):

//слушаем
d3.select(document).on('dataChange', function(data) {  
    console.log(d3.event); //null
    console.log(data.newData);
});
//включаем событие
d3.select(document).on('dataChange')({  
    newData: 'Hello World!'
});


A more general approach is to add a function to the d3 object so that it can be used on any selection.

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


Adding this function to D3 allows you to get a trigger function that resembles that of jQuery, which can be used as follows:

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


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

after () and before ()

Using jQuery, you can insert elements immediately after all the elements in the selection. Consider the code:

  • List
  • List
  • List


You can use the following simple code to insert a new item after each item in a list:

$('li').after('
  • Item
  • ');


    And here is what we get:

    • List
    • Item
    • List
    • Item
    • List
    • Item


    In D3, you have to go through all the elements of the selection and add them via JavaScript:

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


    The best option is to make a general purpose function that adds elements based on the tag name and returns a new selection of the created elements so that they can then be edited:

    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);
    }
    


    By adding the following to your code, you can use the after () function in much the same way as in jQuery:

    d3.selectAll('li')  
        .after('li')
        .text('Item')
        //тут можно сделать со вставленными элементами что-нибудь ещё
    


    The before () function looks almost the same, with the only difference being that elements are inserted before the selection.

    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 ()

    It's simple - the jQuery function deletes all child nodes in the selection.

    • List item
    • List item
    • List item


    $('ul').empty();
    


    And as a result:



      In D3, for this you need to clear the internal HTML of the selected element:

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


      D3 is often used to work with SVG. In this case, this code will not work, since innerHTML is not supported there. Therefore, it is better not to call html (), but to select all the child nodes and delete them:

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


      General purpose code will be simple. I chose a different name for the function than that used in jQuery, since D3 already has its own empty () function.

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


      Now you can clear the selection in much the same way as in jQuery:

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


      appendTo ()

      In jQuery, this function works almost the same as the append () function in D3, but it adds the preceding selected items to another selection. To do this in D3, you need to go through all the elements in both selections and add the elements to each other. If you have several goals to which you need to add a selection, you will have to clone objects to get behavior similar to jQuery. That's what I did:

      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;
      }
      


      Using it, you can add multiple elements to the DOM. Work example:

      some element
      some other element


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

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


      What will happen in the DOM:

      some element
      some other element
      some element
      some other element


      The function returns the added elements so that you can work with them further. For example, changing the background:

      d3.selectAll ('. Foo'). AppendTo ('. Target'). Style ('background', '# f00');

      length ()

      It is sometimes useful to know how many elements are in your selection. jQuery has a property called length


      $('.foo').length; //2
      


      Same thing in D3:

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


      With this code, you can do this:

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


      toggleClass ()

      As already mentioned, in D3, you can use the classed function to manage class names. But in D3 there is no function for switching class names, which is often used in jQuery. Its implementation may be as follows:

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


      eq ()

      To filter a selection of several elements and select only a node with a given index, you can use the eq () function in jQuery. It is quite simple to do for D3. We make a subsample of the elements based on the index and return the newly made selection:

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


      show () / hide () / toggle ()

      Used to change the visibility of an element on the page. They simply change the styles of the selected elements. And in the toggle () function, you first need to check whether this element is visible.

      Show hidden:

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


      Hide visible:

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


      Toggle Visibility:

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


      moveToFront (), moveToBack ()

      These features are often missing in D3, but they are not related to jQuery. D3 is often used to work with SVG. At the same time, unlike HTML, in SVG the order of elements determines their visibility. Therefore, we often do not have enough functionality to move the selection in the SVG backward or forward.

      To do this, we expand the D3 samples with the following functions:

      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); 
              } 
          });
      };
      


      Using them is extremely simple - select the svg element and move it where necessary:

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


      We hope that the above will be useful in those projects in which the excessive use of jQuery can be replaced by simple solutions on D3. And an already expanded version of D3 with the included features can be taken on GitHub.

      Also popular now: