Create a gist now

Instantly share code, notes, and snippets.

Backbone D3View II

This experiment extends the previuos one by handling model changes. By clicking on one of the three SVG, the model hue attribute is randomically changed. Since the views (SVGs) are listening to model changes (through the listenTo function), the hue of all of them is automatically changed by Backbone.

// 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
# Bind a DOM events to a certain function
events:
click: 'change_hue'
#
initialize: () ->
# store current pixel width and height
this.width = this.el.getBoundingClientRect().width
this.height = this.el.getBoundingClientRect().height
this.d3el.append 'rect'
# listen to any attribute change in the model
# specific attribute can be listened to using 'change:attribute_name'
this.listenTo this.model, 'change', this.render
this.render()
render: () ->
color = this.model.attributes.hue
this.d3el.select 'rect'
.attr
x: PADDING
y: PADDING
width: this.width - 2*PADDING
height: this.height - 2*PADDING
fill: d3.hcl color, 40, 60
this.d3el
.style
background: d3.hcl color, 20, 30
change_hue: () ->
this.model.change_hue()
// Generated by CoffeeScript 1.10.0
(function() {
var PADDING;
PADDING = 20;
window.FakeChart = Backbone.D3View.extend({
events: {
click: 'change_hue'
},
initialize: function() {
this.width = this.el.getBoundingClientRect().width;
this.height = this.el.getBoundingClientRect().height;
this.d3el.append('rect');
this.listenTo(this.model, 'change', this.render);
return this.render();
},
render: function() {
var color;
color = this.model.attributes.hue;
this.d3el.select('rect').attr({
x: PADDING,
y: PADDING,
width: this.width - 2 * PADDING,
height: this.height - 2 * PADDING,
fill: d3.hcl(color, 40, 60)
});
return this.d3el.style({
background: d3.hcl(color, 20, 30)
});
},
change_hue: function() {
return this.model.change_hue();
}
});
}).call(this);
window.Color = Backbone.Model.extend
defaults: () ->
hue: 250
change_hue: () ->
this.set 'hue', Math.random()*250
// Generated by CoffeeScript 1.10.0
(function() {
window.Color = Backbone.Model.extend({
defaults: function() {
return {
hue: 250
};
},
change_hue: function() {
return this.set('hue', Math.random() * 250);
}
});
}).call(this);
# thanks to Backbone.D3View, we can add new Views without using $(document).ready
### Models
###
color = new Color()
### Views
###
new FakeChart
el: "#chart_1"
model: color
new FakeChart
el: "#chart_2"
model: color
new FakeChart
el: "#chart_3"
model: color
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;
}
rect:hover {
opacity: 0.6
}
<!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>
<!-- your models go here -->
<script src="color.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
/* Models
*/
(function() {
var color;
color = new Color();
/* Views
*/
new FakeChart({
el: "#chart_1",
model: color
});
new FakeChart({
el: "#chart_2",
model: color
});
new FakeChart({
el: "#chart_3",
model: color
});
}).call(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment