When creating visuals or interactive pages, we often use a combination of jQuery and D3. And D3 is mainly used, and from jQuery they take a small set of functions for DOM manipulations.
And although D3 has powerful features - selectors and ajax wrapper, we often lack some functions from jQuery. We show how you can replace jQuery using D3 everywhere. As a result, your code will be simplified, the size of the project will decrease, and you will not mix different approaches, but you will use functions in the manner that is customary in D3.
To begin, consider what 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);
Management of 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 classes of DOM elements, 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
Inserting child nodes is an important function, especially when rendering input data. This is easy and simple:
jQuery
$('.foo').append('<div/>'); $('.foo').prepend('<div/>');
D3
d3.selectAll('.foo').append('div'); d3.selectAll('.foo').insert('div');
Event Tracking
The same syntax is for tracking events on selected items.
jQuery
$('.foo').on('click', clickHandler);
D3
d3.selectAll('.foo').on('click', clickHandler);
Deleting items
Sometimes you need to remove elements from the DOM. Here is how it is done:
jQuery
$('.foo').remove();
D3
d3.selectAll('.foo').remove();
Selection of a subset of elements
You can select children from a larger sample.
jQuery
$('.foo').find('.bar');
D3
d3.selectAll('.foo').selectAll('.bar');
Content Management
You can use the following functions to change the contents of a DOM node.
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>');
Differences
Now consider the functions that are in jQuery, but not in D3. For each of them, there is a simple solution for replacing it, as well as a more general use case, which can be useful to you anywhere in your application, using proprietary 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 run or track custom events for any item on the page. For example, you can run a custom event with some data for your document, and track it in code:
In D3, this is not directly supported, but such behavior can always be achieved. A simple option (if you don’t need d3.event in the handle):
A more general approach is to add a function to the d3 object so that it can be used on any sample.
d3.selection.prototype.trigger = function(evtName, data) { this.on(evtName)(data); }
Adding this function to D3 allows you to get a trigger function similar to that in 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 ()
With jQuery, you can insert elements immediately after all the elements in the selection. Consider the code:
<ul> <li>List</li> <li>List</li> <li>List</li> </ul>
You can use the following simple code to insert a new item after each list item:
$('li').after('<li>Item</li>');
And that's what we get:
<ul> <li>List</li> <li>Item</li> <li>List</li> <li>Item</li> <li>List</li> <li>Item</li> </ul>
In D3 you have to go through all the elements of the sample and add them through 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 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 that the 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 removes all the child nodes in the sample.
- List item
- List item
- List item
$('ul').empty();
And as a result:
<ul></ul>
In D3, for this you need to clear the internal HTML for the selected element:
d3.selectAll('ul').html('');
D3 is often used to work with SVG. In this case, such code will not work, because innerHTML is not supported there. Therefore, it is better not to call html (), but select all the child nodes and delete them:
d3.selectAll('ul').selectAll('*').remove();
The general purpose code will be simple. I chose a different name for the function than the one used in jQuery, because D3 already has its own function empty ().
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 in much the same way as the append () function in D3, but it adds the preceding selected elements to another sample. To do this in D3, you need to go through all the elements in both samples and add elements to each other. If you have several targets to which you need to add a sample, you will have to clone objects to get behavior similar to jQuery. Here's what I got:
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:
<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 add them to the targets.
d3.selectAll('.foo').appendTo('.target');
What will happen 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 added elements so that you can continue working with them. For example, changing the background:
d3.selectAll ('. foo'). appendTo ('. target'). style ('background', '# f00');
length ()
Sometimes it is useful to know how many items are in your sample. jQuery has a property named length
<div class=".foo"></div> <div class=".foo"></div>
$('.foo').length;
Same thing in D3:
d3.selection.prototype.length = function() { return this[0].length; }
You can do this with this code:
d3.selectAll('.foo').length()
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:
d3.selection.prototype.toggleClass = function(className) { this.classed(className, !this.classed(className)); return this; }
eq ()
To filter a selection of several items and select only a node with a given index, you can use the eq () function in jQuery. It's pretty easy 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 item on a page. They simply change the styles of the selected items. 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 functions are often lacking 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 lack the functionality to move the sample backward or forward in the SVG.
To do this, we will extend 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); } }); };
To use them is extremely simple - select the svg element and move it to the right place:
d3.select('svg rect') .moveToFront() .moveToBack();
We hope that the above will be useful in those projects where excessive use of jQuery can be replaced with simple solutions on D3. And already the expanded version D3 with the included functions can be taken on GitHub.