Skip to content

Instantly share code, notes, and snippets.

@kleem
Last active January 22, 2016 11:17
Show Gist options
  • Save kleem/a2978fc91f754b3fc964 to your computer and use it in GitHub Desktop.
Save kleem/a2978fc91f754b3fc964 to your computer and use it in GitHub Desktop.
Backbone D3View + flex madness
// Backbone.D3View.js 0.3.1
// ---------------
// (c) 2015 Adam Krebs
// Backbone.D3View may be freely distributed under the MIT license.
// For all details and documentation:
// https://github.com/akre54/Backbone.D3View
(function (factory) {
if (typeof define === 'function' && define.amd) { define(['backbone', 'd3'], factory);
} else if (typeof exports === 'object') { module.exports = factory(require('backbone'), require('d3'));
} else { factory(Backbone, d3); }
}(function (Backbone, d3) {
// Cached regex to match an opening '<' of an HTML tag, possibly left-padded
// with whitespace.
var paddedLt = /^\s*</;
var ElementProto = (typeof Element !== 'undefined' && Element.prototype) || {};
var matchesSelector = ElementProto.matches ||
ElementProto.webkitMatchesSelector ||
ElementProto.mozMatchesSelector ||
ElementProto.msMatchesSelector ||
ElementProto.oMatchesSelector;
Backbone.D3ViewMixin = {
// A reference to the d3 selection backing the view.
d3el: null,
namespace: d3.ns.prefix.svg,
$: function(selector) {
return this.el.querySelectorAll(selector);
},
$$: function(selector) {
return this.d3el.selectAll(selector);
},
_removeElement: function() {
this.undelegateEvents();
this.d3el.remove();
},
_createElement: function(tagName) {
var ns = typeof this.namespace === 'function' ? this.namespace() : this.namespace;
return ns ?
document.createElementNS(ns, tagName) :
document.createElement(tagName);
},
_setElement: function(element) {
if (typeof element == 'string') {
if (paddedLt.test(element)) {
var el = document.createElement('div');
el.innerHTML = element;
this.el = el.firstChild;
} else {
this.el = document.querySelector(element);
}
} else {
this.el = element;
}
this.d3el = d3.select(this.el);
},
_setAttributes: function(attributes) {
this.d3el.attr(attributes);
},
// `delegate` supports two- and three-arg forms. The `selector` is optional.
delegate: function(eventName, selector, listener) {
if (listener === undefined) {
listener = selector;
selector = null;
}
var view = this;
var wrapped = function(event) {
var node = event.target,
idx = 0,
o = d3.event;
d3.event = event;
// The `event` object is stored in `d3.event` but Backbone expects it as
// the first argument to the listener.
if (!selector) {
listener.call(view, d3.event, node.__data__, idx++);
d3.event = o;
return;
}
while (node && node !== view.el) {
if (matchesSelector.call(node, selector)) {
listener.call(view, d3.event, node.__data__, idx++);
}
node = node.parentNode;
}
d3.event = o;
};
var map = this._domEvents || (this._domEvents = {});
var handlers = map[eventName] || (map[eventName] = []);
handlers.push({selector: selector, listener: listener, wrapped: wrapped});
this.el.addEventListener(eventName, wrapped, false);
return this;
},
undelegate: function(eventName, selector, listener) {
if (!this._domEvents || !this._domEvents[eventName]) return;
if (typeof selector !== 'string') {
listener = selector;
selector = null;
}
var handlers = this._domEvents[eventName].slice();
var i = handlers.length;
while (i--) {
var handler = handlers[i];
var match = (listener ? handler.listener === listener : true) &&
(selector ? handler.selector === selector : true);
if (!match) continue;
this.el.removeEventListener(eventName, handler.wrapped, false);
this._domEvents[eventName].splice(i, 1);
}
},
undelegateEvents: function() {
var map = this._domEvents, el = this.el;
if (!el || !map) return;
Object.keys(map).forEach(function(eventName) {
map[eventName].forEach(function(handler) {
el.removeEventListener(eventName, handler.wrapped, false);
});
});
this._domEvents = {};
return this;
}
};
Backbone.D3View = Backbone.View.extend(Backbone.D3ViewMixin);
return Backbone.D3View;
}));
PADDING = 20
window.FakeChart = Backbone.D3View.extend
initialize: () ->
# store current pixel width and height
this.width = this.el.getBoundingClientRect().width
this.height = this.el.getBoundingClientRect().height
# store a D3 reference to the base SVG element
this.vis = d3.select(this.el)
# fake visualization content: a random-colored background with a padded rect inside it
# (the size of the rect is computed according to the available space)
hue = Math.random()*360
this.vis.append 'rect'
.attr
x: PADDING
y: PADDING
width: this.width - 2*PADDING
height: this.height - 2*PADDING
fill: d3.hcl(hue, 40, 60)
this.vis
.style
background: d3.hcl(hue, 20, 30)
// Generated by CoffeeScript 1.10.0
(function() {
var PADDING;
PADDING = 20;
window.FakeChart = Backbone.D3View.extend({
initialize: function() {
var hue;
this.width = this.el.getBoundingClientRect().width;
this.height = this.el.getBoundingClientRect().height;
this.vis = d3.select(this.el);
hue = Math.random() * 360;
this.vis.append('rect').attr({
x: PADDING,
y: PADDING,
width: this.width - 2 * PADDING,
height: this.height - 2 * PADDING,
fill: d3.hcl(hue, 40, 60)
});
return this.vis.style({
background: d3.hcl(hue, 20, 30)
});
}
});
}).call(this);
# thanks to Backbone.D3View, we can add new Views without using $(document).ready
new FakeChart
el: "#chart_1"
new FakeChart
el: "#chart_2"
new FakeChart
el: "#chart_3"
html, body {
padding: 0;
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
/* flex layout */
body {
display: flex;
flex-direction: row;
}
#chart_1 {
flex-grow: 2;
width: 0; /* necessary hack. see README */
}
#side {
flex-grow: 1;
width: 0;
display: flex;
flex-direction: column;
}
#chart_2 {
flex-grow: 1;
height: 0;
}
#chart_3 {
flex-grow: 1;
height: 0;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Backbone D3View + flex madness</title>
<link type="text/css" href="index.css" rel="stylesheet"/>
<!-- dependencies -->
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://underscorejs.org/underscore-min.js"></script>
<script src="http://backbonejs.org/backbone-min.js"></script>
<script src="backbone.d3view.js"></script>
<!-- your views go here -->
<script src="chart.js"></script>
</head>
<body>
<svg id="chart_1"></svg>
<div id="side">
<svg id="chart_2"></svg>
<svg id="chart_3"></svg>
</div>
<script src="index.js"></script>
</body>
</html>
// Generated by CoffeeScript 1.10.0
(function() {
new FakeChart({
el: "#chart_1"
});
new FakeChart({
el: "#chart_2"
});
new FakeChart({
el: "#chart_3"
});
}).call(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment