Marrying Backbone.js and D3.js

Shirley Wu
6 min readJul 11, 2015

This article was originally posted in Quora on May 2013.

Last September, I attended a Visualization Hack Day at Rackspace. It was my first serious introduction into D3 (JavaScript library), and I quickly found a teacher in Kai Chang, co-organizer of the Bay Area d3 User Group. It was at the Hack Day that I started to understand the philosophy behind D3, started to understand the enter-update-exit pattern, and marveled at the genius of binding data to the DOM.

But that was only the tip of the iceberg.

Towards the end of the day, I off-handedly asked Kai (what I felt was) an innocuous question — how would I use D3 in Backbone.js? I had just discovered Backbone about a month ago, and was starting to really love its Model-View(-Controller) capabilities; can I integrate D3 into a Backbone View?

I still remember the look he gave me, slightly etched in surprise and muted skepticism. It’s hard, he told me.

The conversation moved on, and the sidetrack was quickly forgotten.

Fast-forward about 6 months, and I find myself again asking the question, how do I use D3 and Backbone together? This time around, I found myself asking it for work.

The problem.

My current project at work consists of making a single-page web application for editing our current view system. It’s an exciting project, because our view system uses XML, and everyone hates writing XML. But also, visualizing XML means trees, and visualizing trees in the browser means D3.

That’s perfectly fine, because visualizations like these already exist:

Collapsible Tree Layout

All I have to do is replace those circles with some rectangles and maybe an icon image or two for interactivity.

But the spec becomes more complicated because each of those nodes represent modules in our view hierarchy that have parameters and attributes associated with them. And when so much of the data have to be rendered and edited, it no longer makes sense to bind them to the DOM.

Enter Backbone.

With Backbone, I can easily create a model, pass it to the view and have the view listen to changes on the model. The DOM need not know any more than necessary to render the visualization with D3 (in my case, it just needed the x-position, y-position, and the module name to display), because the Backbone model will keep track of the other data (parameters and attributes, for example) for me.

So now all I have to do is create a Backbone view wrapped around an SVG element, right?

Heh.

The problem, further.

I tried very hard to render SVG elements while extending Backbone Views. With any other elements, I follow (at the very basic) the following pattern:

var MyView = Backbone.View.extend(function() {
render: function() {
this.$el.append(_.template(Template));
return this;
}
});

And then somewhere else (hopefully the container view), I’d say:

var myView = new MyView();
this.$el.append(myView.render().el);

Pretty standard stuff.

But when it comes to SVG elements, no matter how hard I try to do this:

var MyRectView = Backbone.View.extend(function() {
el: “rect”,
render: function() {
return this;
}
});
var myRectView = new MyRectView();
$(“svg”).append(myRectView.render().el);

It would not render me a rectangle. (Or a circle, or a path, or anything.)

It turns out to be a namespacing issue. When a document has multiple XML dialects, some tag names might start to conflict — how would a user agent know which dialect the tag name belongs to? (For more: Namespaces Crash Course)

In particular, SVG is namespaced to http://www.w3.org/2000/svg.

However, when you create a new Backbone View, it automatically assigns its element namespace to http://www.w3.org/1999/xhtml. No matter that you declared the element to be a rect element. By the time Backbone reads that the element should be an SVG element, the XHTML namespace is already set in stone.

The solution, sort of.

In my search for a solution, I came across a StackOverflow thread that was particularly helpful: SVG not rendering properly as a backbone view.

But what Mike Bostock suggests in his promoted answer, is to either overwrite Backbone’s View.make function (which I can no longer find — help?), or to wrap the SVG in a DIV element, presumably wrapped in a Backbone View.

My co-worker came up with another solution, where the SVG element is first created with D3, and then passed to the Backbone View upon initialization.

var rect = d3.select(“svg”).append(“rect”);
var myRectElement = new MyRectElement({el: rect});

D3's append is different from JQuery’s append in that it both creates and inserts into the DOM. This is nice, since it creates the element and automatically namespaces it to SVG for you. (The downside is that it’s not as flexible, since you cannot append a pre-existing element with D3). The above code takes advantage of this D3 feature, so that when the Backbone view is created, the view’s element is assigned the rect element that has already been namespaced to SVG instead of creating a new one.

And now I can use Backbone’s view.el to reference the rect element like any other DOM element, and wrap it in a jQuery object with view.$el. For convenience, I can also create a D3 wrapper.

var MyRectView = Backbone.View.extend(function() {
initialize: function() {
this.d3 = d3.select(this.el);
},
render: function() {
return this;
}
});

And this brings me to my next conundrum.

When should I use what?

There’s a lot of overlap between D3 and Backbone (and thus, jQuery), and both have their nuances. I’ve already mentioned the nuance with append, where jQuery only inserts, while D3 inserts and creates.

When I select, when I remove, when I bind events, should I be using D3 or jQuery? In particular, I’ve been trying to figure out how I should be binding events, and I have been fluctuating between two methods.

The first I giddily tried to use was Backbone’s Events module (which I quite love). I mean, I just worked so hard to wrap the element in Backbone, might as well take advantage of Backbone features, yah?

var MyGroupView = Backbone.View.extend(function() {
initialize: function() {
this.d3 = d3.select(this.el);
},
render: function() {
this.d3.append(“rect”).classed(“myRect”, true);
return this;
},
events: {
“click rect”: “doSomething”,
“click .myRect”: “doSomethingElse”
}
});
var g = d3.select(“svg”).append(“g”);
var myGroupView = new MyGroupView({el: g});

The first event assignment works; when I click on the rectangle nested within my group element, the doSomething function is triggered. But when I click on that same rectangle, doSomethingElse is not triggered. I have yet to look too deeply into why Backbone cannot pick up the rectangle’s class name, but I believe D3 stores class names differently from jQuery.

When I hit that error, I switched back to D3's way of event binding.

var MyGroupView = Backbone.View.extend(function() {
initialize: function() {
this.d3 = d3.select(this.el);
},
render: function() {
this.d3.append(“rect”).classed(“myRect”, true)
.on(“click”, _.bind(this.doSomething, this));
return this;
}
});
var g = d3.select(“svg”).append(“g”);
var myGroupView = new MyGroupView({el: g});

(Note that because I bound the event with D3, the function doSomething will have for the value of this the element that triggered the event — in this case, the rectangle with class name “myRect”. Because I want the value of this to refer to the Backbone view itself, I use Underscore’s very convenient _.bind method).

EDIT: Jyri Tuulos has provided a great solution for the class name conundrum, documented here: https://github.com/jquery/jquery...

events: { 
“click [class~=myRect]”: doSomethingElse
}

So?

So I’m still working on it. Every day I’m working on marrying D3.js with Backbone.js. It’s far from perfect. I have a lot more questions than answers. I think this blog post poses more questions than answers. Sometimes I get insecure that the cons of marrying them far outweigh the pros.

But all marriages take work, and I’m putting in the effort.

I wrote this post seeking advice, solutions, suggestions, best practices — anything to improve my code quality. Responses are very very welcome.

EDIT: for those interested, I’ve put up a simple example on both JSFiddle and JSBin:
http://jsfiddle.net/shirleyxueya...
http://jsbin.com/ucahef/46/edit

--

--

Shirley Wu

I code too much & don’t draw enough. @ucberkeley alum, 1/2 @datasketches, @d3bayarea & @d3unconf co-organizer ✨ currently freelancing → http://sxywu.com