Skip to content

Instantly share code, notes, and snippets.

@kleem
Last active January 22, 2016 23:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kleem/b97e3930c653ad9eb120 to your computer and use it in GitHub Desktop.
Save kleem/b97e3930c653ad9eb120 to your computer and use it in GitHub Desktop.
Bar charts (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;
}));
// Generated by CoffeeScript 1.10.0
(function() {
window.Chart = Backbone.D3View.extend({
initialize: function(conf) {
this.width = this.el.getBoundingClientRect().width;
this.height = this.el.getBoundingClientRect().height;
return this.d3el.classed('chart', true);
}
});
window.BarChart = Chart.extend({
scales: {
x: d3.scale.linear(),
y: d3.scale.ordinal(),
color: d3.scale.category10()
},
initialize: function(conf) {
Chart.prototype.initialize.apply(this, conf);
this.d3el.classed('bar_chart', true);
this.scales.x.range([0, this.width]);
this.scales.y.rangeRoundBands([0, this.height], 0.05);
return this.render();
},
render: function() {
var bars, data;
data = this.model.attributes.data;
this.scales.x.domain([0, d3.max(data)]);
this.scales.y.domain(data.map(function(d, i) {
return i;
}));
bars = this.d3el.selectAll('.bar').data(data);
bars.enter().append('rect').attr({
x: 0
});
bars.attr({
y: (function(_this) {
return function(d, i) {
return _this.scales.y(i);
};
})(this),
width: (function(_this) {
return function(d) {
return _this.scales.x(d);
};
})(this),
height: this.scales.y.rangeBand(),
fill: (function(_this) {
return function(d, i) {
return _this.scales.color(i);
};
})(this)
});
return bars.exit().remove();
}
});
}).call(this);
// 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);
// Generated by CoffeeScript 1.10.0
(function() {
window.Data = Backbone.Model.extend({
defaults: function() {
return {
data: [20, 6, 12, 8, 4, 1, 2, 2, 1, 24, 7, 9, 5]
};
}
});
}).call(this);
d1 = new Data
d2 = new Data
data: [1,2,3,6,2,1,4,5]
new BarChart
el: "#chart"
model: d1
new BarChart
el: "#chart2"
model: d1
new BarChart
el: "#chart3"
model: d2
html, body {
padding: 0;
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
/* flex layout */
body {
display: flex;
flex-direction: row;
}
#chart {
margin: 10px;
flex-grow: 2;
width: 0; /* necessary hack. see README */
}
#side {
flex-grow: 1;
width: 0;
display: flex;
flex-direction: column;
}
#chart2 {
margin: 10px;
flex-grow: 1;
height: 0; /* necessary hack. see README */
}
#chart3 {
margin: 10px;
flex-grow: 1;
height: 0; /* necessary hack. see README */
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Bar Charts (Backbone)</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>
<script src="views.js"></script>
<link type="text/css" href="views.css" rel="stylesheet"/>
<script src="models.js"></script>
</head>
<body>
<svg id="chart"></svg>
<div id="side">
<svg id="chart2"></svg>
<svg id="chart3"></svg>
</div>
<script src="index.js"></script>
</body>
</html>
// Generated by CoffeeScript 1.10.0
(function() {
var d1, d2;
d1 = new Data;
d2 = new Data({
data: [1, 2, 3, 6, 2, 1, 4, 5]
});
new BarChart({
el: "#chart",
model: d1
});
new BarChart({
el: "#chart2",
model: d1
});
new BarChart({
el: "#chart3",
model: d2
});
}).call(this);
window.Data = Backbone.Model.extend
defaults: () ->
data: [20,6,12,8,4,1,2,2,1,24,7,9,5]
selection: null
// Generated by CoffeeScript 1.10.0
(function() {
window.Data = Backbone.Model.extend({
defaults: function() {
return {
data: [20, 6, 12, 8, 4, 1, 2, 2, 1, 24, 7, 9, 5],
selection: null
};
}
});
}).call(this);
window.Chart = Backbone.D3View.extend
initialize: (conf) ->
# store current pixel width and height
@width = @el.getBoundingClientRect().width
@height = @el.getBoundingClientRect().height
@d3el.classed 'chart', true
window.BarChart = Chart.extend
initialize: (conf) ->
Chart.prototype.initialize.apply(this, conf)
@d3el.classed 'bar_chart', true
@scales =
x: d3.scale.linear()
y: d3.scale.ordinal()
color: d3.scale.category10()
@scales.x
.range([0, @width])
@scales.y
.rangeRoundBands([0, @height], 0.05)
@listenTo @model, 'change:data', @render
@listenTo @model, 'change:selected_index', @update_selected
@render()
render: () ->
data = @model.attributes.data
@scales.x
.domain([0, d3.max data])
@scales.y
.domain(data.map (d,i) -> i)
bars = @d3el.selectAll '.bar'
.data data
bars.enter().append 'rect'
.attr
class: 'bar'
x: 0
.on 'mouseover', (d,i) =>
@model.set
selected_index: i
.on 'mouseout', () =>
@model.set
selected_index: null
bars
.attr
y: (d,i) => @scales.y(i)
width: (d) => @scales.x(d)
height: @scales.y.rangeBand()
fill: (d,i) => @scales.color(i)
bars.exit()
.remove()
update_selected: () ->
@d3el.selectAll '.bar'
.classed 'focus', (d,i) => i is @model.attributes.selected_index
.bar_chart .focus {
fill: black;
}
// Generated by CoffeeScript 1.10.0
(function() {
window.Chart = Backbone.D3View.extend({
initialize: function(conf) {
this.width = this.el.getBoundingClientRect().width;
this.height = this.el.getBoundingClientRect().height;
return this.d3el.classed('chart', true);
}
});
window.BarChart = Chart.extend({
initialize: function(conf) {
Chart.prototype.initialize.apply(this, conf);
this.d3el.classed('bar_chart', true);
this.scales = {
x: d3.scale.linear(),
y: d3.scale.ordinal(),
color: d3.scale.category10()
};
this.scales.x.range([0, this.width]);
this.scales.y.rangeRoundBands([0, this.height], 0.05);
this.listenTo(this.model, 'change:data', this.render);
this.listenTo(this.model, 'change:selected_index', this.update_selected);
return this.render();
},
render: function() {
var bars, data;
data = this.model.attributes.data;
this.scales.x.domain([0, d3.max(data)]);
this.scales.y.domain(data.map(function(d, i) {
return i;
}));
bars = this.d3el.selectAll('.bar').data(data);
bars.enter().append('rect').attr({
"class": 'bar',
x: 0
}).on('mouseover', (function(_this) {
return function(d, i) {
return _this.model.set({
selected_index: i
});
};
})(this)).on('mouseout', (function(_this) {
return function() {
return _this.model.set({
selected_index: null
});
};
})(this));
bars.attr({
y: (function(_this) {
return function(d, i) {
return _this.scales.y(i);
};
})(this),
width: (function(_this) {
return function(d) {
return _this.scales.x(d);
};
})(this),
height: this.scales.y.rangeBand(),
fill: (function(_this) {
return function(d, i) {
return _this.scales.color(i);
};
})(this)
});
return bars.exit().remove();
},
update_selected: function() {
return this.d3el.selectAll('.bar').classed('focus', (function(_this) {
return function(d, i) {
return i === _this.model.attributes.selected_index;
};
})(this));
}
});
}).call(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment