Introduction

D3 and jQuery utilize distinctly different approaches to node manipulation and internal array storage. However, there are mechanisms available for converting between D3 and jQuery, and vice versa.

D3 and jQuery Internals

Let’s assume the following HTML structure:

<div class="test-node">Test One</div>
<div class="test-node">Test Two</div>
<div class="test-node">Test Three</div>
<div class="test-node">Test Four</div>

Now let’s retrieve the “test-node” elements with jQuery:

var testNodes = $(".test-node");
console.log(testNodes);

As can be seen above, jQuery stores the selected elements in a first level array, making elements accessible by testNodes[0...3] as expected.  Now let’s examine the D3 equivalent:

var testNodes = d3.selectAll(".test-node");
console.log(testNodes);

When using D3, the selected elements get stored in a second level array making the elements accessible by testNode[0][0...n].

Converting from jQuery to D3

var $testNodes = $(".test-node");  //jquery
var testNodes = d3.selectAll( $testNodes.toArray() ); //d3
console.log(testNodes);

The above first retrieves the nodes using standard jQuery selector syntax.  Next the toArray() method is called returning the native array of selected elements.  Finally the d3.selectAll() method is invoked using the native array as the argument.  This forces the native element array into the second level array of D3.

Converting from D3 to jQuery

var testNodes = d3.selectAll(".test-node");
var $testNodes = $(testNodes[0]);
console.log($testNodes);

The above first retrieves the nodes using D3 selector syntax.  Next the first level D3 array is used as the argument passed into the jQuery selector.

Class Addition/Removal

Adding and removing a class in jQuery is pretty straightforward:

$(".test-node").addClass("selected"); //add class "selected"
$(".test-node").removeClass("selected"); //remove class "selected"

The same performed in D3 is performed using the following:

d3.selectAll(".test-node").classed("selected", true); //add class "selected"
d3.selectAll(".test-node").classed("selected", false); //remove class "selected"

Data/Datum

D3 has a very elegant data system whereby information can be bound to elements by use of the datum() method.

D3 Datum

Let’s examine the following:

var nodes = d3.selectAll(".test-node")
    .datum({key: "hello", value: "world"})
    .append("span");

console.log(nodes);

The above example selects all of the .test-node div classed elements utilizing D3, initializes a data structure/object and passes this structure/object to the span elements which get appended to all the matching .test-node div classed elements.  This datum() method passes the data structure/object to the span elements using a property called __data__.  As displayed above, the __data__ property has the structure of:

__data__: {
    key: "hello",
    value: "world"
}

The __data__ property can be accessed directly using second level array syntax (ie. d3.selectAll("span")[0][0...n].__data__) or it can be utilized as a default argument passed into nearly all D3 selector instance methods.  Let’s examine an instance method where this takes place:

var nodes = d3.selectAll(".test-node")
    .datum({key: "hello", value: "world"})
    .append("span")
    .text(function(d){
        return d.key + " " + d.value;
    });

In the above example, the only difference from the previous example is the instance method text(), whereby the argument passed is a function.  This function defines an argument d which D3 passes to these type of instance methods automatically.  The d argument being the actual data/datum used to initialize the span elements.  The body of the text function returns the string hello world using the data/datum, thusly setting the text of the span elements to hello world.

jQuery Data

jQuery has a simple mechanism for adding arbitrary data to elements by use of the data() method.  Let’s examine the following:

var nodes = $(".test-node")
    .append("span")
    .data("datum", {key: "hello", value: "world"});

console.log(nodes);
console.log($.cache);

In the above example, the .test-node elements are retrieved, span elements are appended and the datum {key: "hello", value: "world"} is added to the data.  The console output reveals that the datum is stored in the $.cache in jQuery.

Exchanging D3 Datum with jQuery

Now that we’ve seen how both D3 and jQuery store data/datum, let’s exchange D3 datum with jQuery:

//D3
var nodes = d3.selectAll(".test-node")
    .datum({key: "hello", value: "world"})
    .append("span");

console.log(nodes);

//jQuery
var $nodes = $(nodes[0]);
$nodes.each(function(){
    $(this).data("datum", $(this).prop("__data__"));
});

console.log($nodes);
console.log($.cache);

In this example, we first select the .test-node classed elements using D3, append span elements using the datum/data specified.  Next, we create a jQuery selector using the first level array of the D3 selector.  Using the jQuery selector, the nodes are iterated over in each and the data() of the jQuery element is set to the __data__ property of the D3 element.

Exchanging jQuery Data With D3

Now, let’s exchange jQuery data with D3 using a similar technique to the previous example:

//jQuery
var $nodes = $(".test-node")
    .append("span")
    .data("datum", {key: "hello", value: "world"});

console.log($nodes);
console.log($.cache);

//D3
var nodes = d3.selectAll($nodes.toArray())
    .datum({})
    .each(function(d){
        this.__data__ = $(this).data("datum");
    });

console.log(nodes);

In the above example, the .test-node elements are selected using jQuery, the span child elements are appended and the data() method is used to store the datum {key: "hello", value: "world"}.  Next, the D3 selector is populated using the toArray() method of the jQuery selector, the datum() method is used to initialize an empty data object and finally the nodes are iterated over and the __data__ property is populated with the jQuery data datum.

Conclusion

Both frameworks are very powerful and can live together in peace.  Information can be exchanged freely between the the two frameworks utilizing these or similar techniques.


8 Comments

kurd · July 6, 2014 at 12:57 pm

Nice research and comparisons. Thank you!

bert · July 19, 2014 at 7:49 am

there’s an error in the d3 sample of the section ‘Class Addition/Removal’. instead of $ it shoud be d3.selectAll or d3.select.

    godlikemouse · July 21, 2014 at 11:50 am

    Hi bert,

    Thank you for finding that, it has been fixed.

adamk · July 22, 2014 at 9:13 pm

Thanks for this post – very helpful. However, one thing that hung me up was the following: Under various circumstances a D3 selection can include nulls. For example, entering and exiting selections often do. This works fine in D3 because the operators for selections expect nulls and handle them smoothly. However, as far as I can tell, jQuery methods doesn’t expect nulls in selections, and so they cause problems. In your “Converting from D3 to jQuery” section, the use of the $(testNodes[0]) idiom will leave any nulls in the selection, causing any subsequent jQuery methods called on that selection to throw errors. So, if there is any chance that a D3 selection that you are converting to a jQuery selection might have nulls in it, I would recommend using $(testNodes.filter(function(d) {return d !== null;})[0]). This filters out all of the nulls from the selection. Of course, this may also change the indices of the non-null elements within the selection, so if you are depending on those, then you may need to take another approach.

Ronan · July 23, 2014 at 5:31 am

Thank you so much. This was the first hit I found actually showing element exchange between JQuery and D3

    godlikemouse · July 23, 2014 at 9:10 am

    You’re very welcome, glad it was helpful 🙂

bob · April 18, 2016 at 12:10 am

“.toArray() is not a function.”
At least not anymore. Don’t know how to convert it with current versions of both libs.

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *