generator-d3chart
Version:
A Yeoman generator for d3.chart
422 lines (344 loc) • 10.3 kB
JavaScript
/*! d3.chart - v0.0.1
* License: MIT Expat
* Date: 2013-05-27
*/
(function(window, undefined) {
"use strict";
var d3 = window.d3;
var errors = {
noBase: new Error("d3.layer must be initialized with a base"),
noDataBind: new Error("d3.layer must specify a `dataBind` method."),
noInsert: new Error("d3.layer must specify an `insert` method.")
};
var Layer = function(base) {
if (!base) {
throw errors.noBase;
}
this._base = base;
this._handlers = {};
};
// dataBind
Layer.prototype.dataBind = function() {
throw errors.noDataBind;
};
// insert
Layer.prototype.insert = function() {
throw errors.noInsert;
};
// on
// Attach the specified handler to the specified event type.
Layer.prototype.on = function(eventName, handler, options) {
options = options || {};
if (!(eventName in this._handlers)) {
this._handlers[eventName] = [];
}
this._handlers[eventName].push({
callback: handler,
chart : options.chart || null
});
};
// off
// Remove the specified handler. If no handler is supplied, remove *all*
// handlers from the specified event type.
Layer.prototype.off = function(eventName, handler) {
var handlers = this._handlers[eventName];
var idx;
if (!handlers) {
return;
}
if (arguments.length === 1) {
handlers.length = 0;
return;
}
for (idx = handlers.length - 1; idx > -1; --idx) {
if (handlers[idx].callback === handler) {
handlers.splice(idx, 1);
}
}
};
// draw
// Bind the data to the layer, make lifecycle selections, and invoke all
// relevant handlers.
Layer.prototype.draw = function(data) {
var bound, entering, events, selection, handlers, eventName, idx, len;
bound = this.dataBind.call(this._base, data);
if (!(bound instanceof d3.selection)) {
throw new Error('Invalid selection defined by `dataBind` method.');
}
entering = bound.enter();
entering._chart = this._base._chart;
events = [
{
name: 'update',
selection: bound
},
{
name: 'enter',
// Defer invocation of the `insert` method so that the previous
// `update` selection does not contain the new nodes.
selection: this.insert.bind(entering)
},
{
name: 'merge',
// This selection will be modified when the previous selection
// is made.
selection: bound
},
{
name: 'exit',
selection: bound.exit.bind(bound)
}
];
for (var i = 0, l = events.length; i < l; ++i) {
eventName = events[i].name;
selection = events[i].selection;
// Some lifecycle selections are expressed as functions so that
// they may be delayed.
if (typeof selection === 'function') {
selection = selection();
}
if (!(selection instanceof d3.selection)) {
throw new Error('Invalid selection defined for "' + eventName +
"' lifecycle event.");
}
handlers = this._handlers[eventName];
if (handlers) {
for (idx = 0, len = handlers.length; idx < len; ++idx) {
// Attach a reference to the parent chart so the selection"s
// `chart` method will function correctly.
selection._chart = handlers[idx].chart || this._base._chart;
selection.call(handlers[idx].callback);
}
}
handlers = this._handlers[eventName + ":transition"];
if (handlers && handlers.length) {
selection = selection.transition();
for (idx = 0, len = handlers.length; idx < len; ++idx) {
selection._chart = handlers[idx].chart || this._base._chart;
selection.call(handlers[idx].callback);
}
}
}
};
d3.selection.prototype.layer = function(options) {
var layer = new Layer(this);
var eventName;
// Set layer methods (required)
layer.dataBind = options.dataBind;
layer.insert = options.insert;
// Bind events (optional)
if ("events" in options) {
for (eventName in options.events) {
layer.on(eventName, options.events[eventName]);
}
}
// Mix the public methods into the D3.js selection (bound appropriately)
this.on = function() { return layer.on.apply(layer, arguments); };
this.off = function() { return layer.off.apply(layer, arguments); };
this.draw = function() { return layer.draw.apply(layer, arguments); };
return this;
};
}(this));
(function(window, undefined) {
"use strict";
var d3 = window.d3;
var Surrogate = function(ctor) { this.constructor = ctor; };
var variadicNew = function(Ctor, args) {
var inst;
Surrogate.prototype = Ctor.prototype;
inst = new Surrogate(Ctor);
Ctor.apply(inst, args);
return inst;
};
// extend
// Borrowed from Underscore.js
function extend(object) {
var argsIndex, argsLength, iteratee, key;
if (!object) {
return object;
}
argsLength = arguments.length;
for (argsIndex = 1; argsIndex < argsLength; argsIndex++) {
iteratee = arguments[argsIndex];
if (iteratee) {
for (key in iteratee) {
object[key] = iteratee[key];
}
}
}
return object;
}
// initCascade
// Call the initialize method up the inheritance chain, starting with the
// base class and continuing "downward".
var initCascade = function(instance, args) {
var sup = this.constructor.__super__;
if (sup) {
initCascade.call(sup, instance, args);
}
// Do not invoke the `initialize` method on classes further up the
// prototype chain.
if (Object.hasOwnProperty.call(this.constructor.prototype, "initialize")) {
this.initialize.apply(instance, args);
}
};
var Chart = function(selection) {
this.base = selection;
this._layers = {};
this._mixins = [];
this._events = {};
initCascade.call(this, this, Array.prototype.slice.call(arguments, 1));
};
Chart.prototype.unlayer = function(name) {
var layer = this.layer(name);
var idx = this._layerList.indexOf(layer);
delete this._layers[name];
this._layerList.splice(idx, 1);
return this;
};
Chart.prototype.layer = function(name, selection, options) {
var layer;
if (arguments.length === 1) {
return this._layers[name];
}
layer = selection.layer(options);
this._layers[name] = layer;
selection._chart = this;
return layer;
};
Chart.prototype.initialize = function() {};
Chart.prototype.transform = function(data) {
return data;
};
Chart.prototype.mixin = function(chartName, selection) {
var args = Array.prototype.slice.call(arguments, 2);
args.unshift(selection);
var ctor = Chart[chartName];
var chart = variadicNew(ctor, args);
this._mixins.push(chart);
return chart;
};
Chart.prototype.draw = function(data) {
var layerName, mixinName;
data = this.transform(data);
for (layerName in this._layers) {
this._layers[layerName].draw(data);
}
for (mixinName in this._mixins) {
this._mixins[mixinName].draw(data);
}
};
Chart.prototype.on = function(name, callback, context) {
var events = this._events[name] || (this._events[name] = []);
events.push({
callback: callback,
context: context || this,
_chart: this
});
return this;
};
Chart.prototype.once = function(name, callback, context) {
var self = this;
var once = function() {
self.off(name, once);
callback.apply(this, arguments);
};
return this.on(name, once, context);
};
Chart.prototype.off = function(name, callback, context) {
var names, n, events, event, i, j;
// remove all events
if (arguments.length === 0) {
for (name in this._events) {
this._events[name].length = 0;
}
return this;
}
// remove all events for a specific name
if (arguments.length === 1) {
events = this._events[name];
if (events) {
events.length = 0;
}
return this;
}
// remove all events that match whatever combination of name, context
// and callback.
names = name ? [name] : Object.keys(this._events);
for (i = 0; i < names.length; i++) {
n = names[i];
events = this._events[n];
j = events.length;
while (j--) {
event = events[j];
if ((callback && callback === event.callback) ||
(context && context === event.context)) {
events.splice(j, 1);
}
}
}
return this;
};
Chart.prototype.trigger = function(name) {
var args = Array.prototype.slice.call(arguments, 1);
var events = this._events[name];
var i, ev;
for (i = 0; i < events.length; i++) {
ev = events[i];
ev.callback.apply(ev.context, args);
}
return this;
};
Chart.extend = function(name, protoProps, staticProps) {
var parent = this;
var child;
// The constructor function for the new subclass is either defined by
// you (the "constructor" property in your `extend` definition), or
// defaulted by us to simply call the parent's constructor.
if (protoProps && Object.hasOwnProperty.call(protoProps, "constructor")) {
child = protoProps.constructor;
} else {
child = function(){ return parent.apply(this, arguments); };
}
// Add static properties to the constructor function, if supplied.
extend(child, parent, staticProps);
// Set the prototype chain to inherit from `parent`, without calling
// `parent`'s constructor function.
var Surrogate = function(){ this.constructor = child; };
Surrogate.prototype = parent.prototype;
child.prototype = new Surrogate();
// Add prototype properties (instance properties) to the subclass, if
// supplied.
if (protoProps) { extend(child.prototype, protoProps); }
// Set a convenience property in case the parent's prototype is needed
// later.
child.__super__ = parent.prototype;
Chart[name] = child;
return child;
};
// d3.chart
// A factory for creating chart constructors
d3.chart = function(name) {
if (arguments.length === 0) {
return Chart;
} else if (arguments.length === 1) {
return Chart[name];
}
return Chart.extend.apply(Chart, arguments);
};
d3.selection.prototype.chart = function(chartName) {
// Without an argument, attempt to resolve the current selection's
// containing d3.chart.
if (arguments.length === 0) {
return this._chart;
}
var chartArgs = Array.prototype.slice.call(arguments, 1);
chartArgs.unshift(this);
var ChartCtor = Chart[chartName];
return variadicNew(ChartCtor, chartArgs);
};
d3.selection.enter.prototype.chart = function() {
return this._chart;
};
d3.transition.prototype.chart = d3.selection.enter.prototype.chart;
}(this));