I'm using d3.js to render a map of the world in svg (using https://github.com/johan/world.geo.json/blob/master/countries.geo.json for the features). I am encapsulating the rendering logic in a Backbone View. When I render the view and attach it to the DOM, nothing displays in my browser, though the SVG markup is correctly generated when looking at the generated HTML. This renders fine when not encapsulating within a Backbone.View. Here's my code using Backbone.view:
/**
* SVG Map view
*/
var MapView = Backbone.View.extend({
tagName: 'svg',
translationOffset: [480, 500],
zoomLevel: 1000,
/**
* Sets up the map projector and svg path generator
*/
initialize: function() {
this.projector = d3.geo.mercator();
this.path = d3.geo.path().projection(this.projector);
this.projector.translate(this.translationOffset);
this.projector.scale(this.zoomLevel);
},
/**
* Renders the map using the supplied features collection
*/
render: function() {
d3.select(this.el)
.selectAll('path')
.data(this.options.featureCollection.features)
.enter().append('path')
.attr('d', this.path);
},
/**
* Updates the zoom level
*/
zoom: function(level) {
this.projector.scale(this.zoomLevel = level);
},
/**
* Updates the translation offset
*/
pan: function(x, y) {
this.projector.translate([
this.translationOffset[0] += x,
this.translationOffset[1] += y
]);
},
/**
* Refreshes the map
*/
refresh: function() {
d3.select(this.el)
.selectAll('path')
.attr('d', this.path);
}
});
var map = new MapView({featureCollection: countryFeatureCollection});
map.$el.appendTo('body');
map.render();
Here's the code that works, without using Backbone.View
var projector = d3.geo.mercator(),
path = d3.geo.path().projection(projector),
countries = d3.select('body').append('svg'),
zoomLevel = 1000;
coords = [480, 500];
projector.translate(coords);
projector.scale(zoomLevel);
countries.selectAll('path')
.data(countryFeatureCollection.features)
.enter().append('path')
.attr('d', path);
I've also attached a screenshot of the generated SVG markup. Any idea what could be going wrong here?
Edit - Here's the overridden make method that ended up solving this, per request:
/**
* Custom make method needed as backbone does not support creation of
* namespaced HTML elements.
*/
make: function(tagName, attributes, content) {
var el = document.createElementNS('http://www.w3.org/2000/svg', tagName);
if (attributes) $(el).attr(attributes);
if (content) $(el).html(content);
return el;
}
The issue is that the "svg" element requires a namespace. D3 does this for your automatically; when you append an "svg" element, it uses the namespace "http://www.w3.org/2000/svg". For details, see src/core/ns.js. Backbone, unfortunately, does not appear to support namespaced elements. You'd want to change the view.make method. Then you'd need a namespaceURI property on your view to set the appropriate namespace, or just do it automatically for SVG elements for consistency with the HTML5 parser.
At any rate, a simple fix for your problem is to wrap your SVG in a DIV element, and then use D3 to create the SVG element.
You could simply set the view's element in the initialize function as follows:
Backbone.View.extend({
// This is only for informaiton. The node will
// be raplaced in the initialize function.
tagName: 'svg',
initialize: function () {
this.setElement(
d3.select($('<div/>')[0]).append('svg')[0]
);
}
);
This has the advantage of being explicite.
Check this out http://jsfiddle.net/nocircleno/QsEp2/ from http://nocircleno.com/blog/svg-with-backbone-js/
Backbone.View.extend({
nameSpace: "http://www.w3.org/2000/svg",
_ensureElement: function() {
if (!this.el) {
var attrs = _.extend({}, _.result(this, 'attributes'));
if (this.id) attrs.id = _.result(this, 'id');
if (this.className) attrs['class'] = _.result(this, 'className');
var $el = $(window.document.createElementNS(_.result(this, 'nameSpace'), _.result(this, 'tagName'))).attr(attrs);
this.setElement($el, false);
} else {
this.setElement(_.result(this, 'el'), false);
}
}
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With