| // 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); |
| 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> |
| window.Data = Backbone.Model.extend | |
| defaults: () -> | |
| data: [20,6,12,8,4,1,2,2,1,24,7,9,5] | |
| selection: null | |
| 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 |
| // 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