d3
Version:
A small, free JavaScript library for manipulating documents based on data.
626 lines (532 loc) • 16.4 kB
JavaScript
var d3_select = function(s, n) { return n.querySelector(s); },
d3_selectAll = function(s, n) { return d3_array(n.querySelectorAll(s)); };
// Use Sizzle, if available.
if (typeof Sizzle == "function") {
d3_select = function(s, n) { return Sizzle(s, n)[0]; };
d3_selectAll = function(s, n) { return Sizzle.uniqueSort(Sizzle(s, n)); };
}
var d3_root = d3_selection([[document]]);
d3_root[0].parentNode = document.documentElement;
// TODO fast singleton implementation!
d3.select = function(selector) {
return typeof selector == "string"
? d3_root.select(selector)
: d3_selection([[selector]]); // assume node
};
d3.selectAll = function(selector) {
return typeof selector == "string"
? d3_root.selectAll(selector)
: d3_selection([d3_array(selector)]); // assume node[]
};
function d3_selection(groups) {
function select(select) {
var subgroups = [],
subgroup,
subnode,
group,
node;
for (var j = 0, m = groups.length; j < m; j++) {
group = groups[j];
subgroups.push(subgroup = []);
subgroup.parentNode = group.parentNode;
for (var i = 0, n = group.length; i < n; i++) {
if (node = group[i]) {
subgroup.push(subnode = select(node));
if (subnode && "__data__" in node) subnode.__data__ = node.__data__;
} else {
subgroup.push(null);
}
}
}
return d3_selection(subgroups);
}
function selectAll(selectAll) {
var subgroups = [],
subgroup,
group,
node;
for (var j = 0, m = groups.length; j < m; j++) {
group = groups[j];
for (var i = 0, n = group.length; i < n; i++) {
if (node = group[i]) {
subgroups.push(subgroup = selectAll(node));
subgroup.parentNode = node;
}
}
}
return d3_selection(subgroups);
}
// TODO select(function)?
groups.select = function(selector) {
return select(function(node) {
return d3_select(selector, node);
});
};
// TODO selectAll(function)?
groups.selectAll = function(selector) {
return selectAll(function(node) {
return d3_selectAll(selector, node);
});
};
// TODO preserve null elements to maintain index?
groups.filter = function(filter) {
var subgroups = [],
subgroup,
group,
node;
for (var j = 0, m = groups.length; j < m; j++) {
group = groups[j];
subgroups.push(subgroup = []);
subgroup.parentNode = group.parentNode;
for (var i = 0, n = group.length; i < n; i++) {
if ((node = group[i]) && filter.call(node, node.__data__, i)) {
subgroup.push(node);
}
}
}
return d3_selection(subgroups);
};
groups.map = function(map) {
var group,
node;
for (var j = 0, m = groups.length; j < m; j++) {
group = groups[j];
for (var i = 0, n = group.length; i < n; i++) {
if (node = group[i]) node.__data__ = map.call(node, node.__data__, i);
}
}
return groups;
};
// TODO data(null) for clearing data?
groups.data = function(data, join) {
var enter = [],
update = [],
exit = [];
function bind(group, groupData) {
var i = 0,
n = group.length,
m = groupData.length,
n0 = Math.min(n, m),
n1 = Math.max(n, m),
updateNodes = [],
enterNodes = [],
exitNodes = [],
node,
nodeData;
if (join) {
var nodeByKey = {},
keys = [],
key,
j = groupData.length;
for (i = 0; i < n; i++) {
key = join.call(node = group[i], node.__data__, i);
if (key in nodeByKey) {
exitNodes[j++] = group[i]; // duplicate key
} else {
nodeByKey[key] = node;
}
keys.push(key);
}
for (i = 0; i < m; i++) {
node = nodeByKey[key = join.call(groupData, nodeData = groupData[i], i)];
if (node) {
node.__data__ = nodeData;
updateNodes[i] = node;
enterNodes[i] = exitNodes[i] = null;
} else {
enterNodes[i] = d3_selection_enterNode(nodeData);
updateNodes[i] = exitNodes[i] = null;
}
delete nodeByKey[key];
}
for (i = 0; i < n; i++) {
if (keys[i] in nodeByKey) {
exitNodes[i] = group[i];
}
}
} else {
for (; i < n0; i++) {
node = group[i];
nodeData = groupData[i];
if (node) {
node.__data__ = nodeData;
updateNodes[i] = node;
enterNodes[i] = exitNodes[i] = null;
} else {
enterNodes[i] = d3_selection_enterNode(nodeData);
updateNodes[i] = exitNodes[i] = null;
}
}
for (; i < m; i++) {
enterNodes[i] = d3_selection_enterNode(groupData[i]);
updateNodes[i] = exitNodes[i] = null;
}
for (; i < n1; i++) {
exitNodes[i] = group[i];
enterNodes[i] = updateNodes[i] = null;
}
}
enterNodes.parentNode
= updateNodes.parentNode
= exitNodes.parentNode
= group.parentNode;
enter.push(enterNodes);
update.push(updateNodes);
exit.push(exitNodes);
}
var i = -1,
n = groups.length,
group;
if (typeof data == "function") {
while (++i < n) {
bind(group = groups[i], data.call(group, group.parentNode.__data__, i));
}
} else {
while (++i < n) {
bind(group = groups[i], data);
}
}
var selection = d3_selection(update);
selection.enter = function() {
return d3_selectionEnter(enter);
};
selection.exit = function() {
return d3_selection(exit);
};
return selection;
};
// TODO mask forEach? or rename for eachData?
// TODO offer the same semantics for map, reduce, etc.?
groups.each = function(callback) {
for (var j = 0, m = groups.length; j < m; j++) {
var group = groups[j];
for (var i = 0, n = group.length; i < n; i++) {
var node = group[i];
if (node) callback.call(node, node.__data__, i);
}
}
return groups;
};
function first(callback) {
for (var j = 0, m = groups.length; j < m; j++) {
var group = groups[j];
for (var i = 0, n = group.length; i < n; i++) {
var node = group[i];
if (node) return callback.call(node, node.__data__, i);
}
}
return null;
}
groups.empty = function() {
return !first(function() { return true; });
};
groups.node = function() {
return first(function() { return this; });
};
groups.attr = function(name, value) {
name = d3.ns.qualify(name);
// If no value is specified, return the first value.
if (arguments.length < 2) {
return first(name.local
? function() { return this.getAttributeNS(name.space, name.local); }
: function() { return this.getAttribute(name); });
}
/** @this {Element} */
function attrNull() {
this.removeAttribute(name);
}
/** @this {Element} */
function attrNullNS() {
this.removeAttributeNS(name.space, name.local);
}
/** @this {Element} */
function attrConstant() {
this.setAttribute(name, value);
}
/** @this {Element} */
function attrConstantNS() {
this.setAttributeNS(name.space, name.local, value);
}
/** @this {Element} */
function attrFunction() {
var x = value.apply(this, arguments);
if (x == null) this.removeAttribute(name);
else this.setAttribute(name, x);
}
/** @this {Element} */
function attrFunctionNS() {
var x = value.apply(this, arguments);
if (x == null) this.removeAttributeNS(name.space, name.local);
else this.setAttributeNS(name.space, name.local, x);
}
return groups.each(value == null
? (name.local ? attrNullNS : attrNull) : (typeof value == "function"
? (name.local ? attrFunctionNS : attrFunction)
: (name.local ? attrConstantNS : attrConstant)));
};
groups.classed = function(name, value) {
var re = new RegExp("(^|\\s+)" + d3.requote(name) + "(\\s+|$)", "g");
// If no value is specified, return the first value.
if (arguments.length < 2) {
return first(function() {
re.lastIndex = 0;
return re.test(this.className);
});
}
/** @this {Element} */
function classedAdd() {
var classes = this.className;
re.lastIndex = 0;
if (!re.test(classes)) {
this.className = d3_collapse(classes + " " + name);
}
}
/** @this {Element} */
function classedRemove() {
var classes = d3_collapse(this.className.replace(re, " "));
this.className = classes.length ? classes : null;
}
/** @this {Element} */
function classedFunction() {
(value.apply(this, arguments)
? classedAdd
: classedRemove).call(this);
}
return groups.each(typeof value == "function"
? classedFunction : value
? classedAdd
: classedRemove);
};
groups.style = function(name, value, priority) {
if (arguments.length < 3) priority = "";
// If no value is specified, return the first value.
if (arguments.length < 2) {
return first(function() {
return window.getComputedStyle(this, null).getPropertyValue(name);
});
}
/** @this {Element} */
function styleNull() {
this.style.removeProperty(name);
}
/** @this {Element} */
function styleConstant() {
this.style.setProperty(name, value, priority);
}
/** @this {Element} */
function styleFunction() {
var x = value.apply(this, arguments);
if (x == null) this.style.removeProperty(name);
else this.style.setProperty(name, x, priority);
}
return groups.each(value == null
? styleNull : (typeof value == "function"
? styleFunction : styleConstant));
};
groups.property = function(name, value) {
name = d3.ns.qualify(name);
// If no value is specified, return the first value.
if (arguments.length < 2) {
return first(function() {
return this[name];
});
}
/** @this {Element} */
function propertyNull() {
delete this[name];
}
/** @this {Element} */
function propertyConstant() {
this[name] = value;
}
/** @this {Element} */
function propertyFunction() {
var x = value.apply(this, arguments);
if (x == null) delete this[name];
else this[name] = x;
}
return groups.each(value == null
? propertyNull : (typeof value == "function"
? propertyFunction : propertyConstant));
};
groups.text = function(value) {
// If no value is specified, return the first value.
if (arguments.length < 1) {
return first(function() {
return this.textContent;
});
}
/** @this {Element} */
function textConstant() {
this.textContent = value;
}
/** @this {Element} */
function textFunction() {
this.textContent = value.apply(this, arguments);
}
return groups.each(typeof value == "function"
? textFunction : textConstant);
};
groups.html = function(value) {
// If no value is specified, return the first value.
if (arguments.length < 1) {
return first(function() {
return this.innerHTML;
});
}
/** @this {Element} */
function htmlConstant() {
this.innerHTML = value;
}
/** @this {Element} */
function htmlFunction() {
this.innerHTML = value.apply(this, arguments);
}
return groups.each(typeof value == "function"
? htmlFunction : htmlConstant);
};
// TODO append(node)?
// TODO append(function)?
groups.append = function(name) {
name = d3.ns.qualify(name);
function append(node) {
return node.appendChild(document.createElement(name));
}
function appendNS(node) {
return node.appendChild(document.createElementNS(name.space, name.local));
}
return select(name.local ? appendNS : append);
};
// TODO insert(node, function)?
// TODO insert(function, string)?
// TODO insert(function, function)?
groups.insert = function(name, before) {
name = d3.ns.qualify(name);
function insert(node) {
return node.insertBefore(
document.createElement(name),
d3_select(before, node));
}
function insertNS(node) {
return node.insertBefore(
document.createElementNS(name.space, name.local),
d3_select(before, node));
}
return select(name.local ? insertNS : insert);
};
// TODO remove(selector)?
// TODO remove(node)?
// TODO remove(function)?
groups.remove = function() {
return groups.each(function() {
var parent = this.parentNode;
if (parent) parent.removeChild(this);
});
};
groups.sort = function(comparator) {
comparator = d3_selection_comparator.apply(this, arguments);
for (var j = 0, m = groups.length; j < m; j++) {
var group = groups[j];
group.sort(comparator);
for (var i = 1, n = group.length, prev = group[0]; i < n; i++) {
var node = group[i];
if (node) {
if (prev) prev.parentNode.insertBefore(node, prev.nextSibling);
prev = node;
}
}
}
return groups;
};
// type can be namespaced, e.g., "click.foo"
// listener can be null for removal
groups.on = function(type, listener) {
// parse the type specifier
var i = type.indexOf("."),
typo = i == -1 ? type : type.substring(0, i),
name = "__on" + type;
// remove the old event listener, and add the new event listener
return groups.each(function(d, i) {
if (this[name]) this.removeEventListener(typo, this[name], false);
if (listener) this.addEventListener(typo, this[name] = l, false);
// wrapped event listener that preserves d, i
function l(e) {
var o = d3.event; // Events can be reentrant (e.g., focus).
d3.event = e;
try {
listener.call(this, d, i);
} finally {
d3.event = o;
}
}
});
};
// TODO slice?
groups.transition = function() {
return d3_transition(groups);
};
groups.call = d3_call;
return groups;
}
function d3_selectionEnter(groups) {
function select(select) {
var subgroups = [],
subgroup,
subnode,
group,
node;
for (var j = 0, m = groups.length; j < m; j++) {
group = groups[j];
subgroups.push(subgroup = []);
subgroup.parentNode = group.parentNode;
for (var i = 0, n = group.length; i < n; i++) {
if (node = group[i]) {
subgroup.push(subnode = select(group.parentNode));
subnode.__data__ = node.__data__;
} else {
subgroup.push(null);
}
}
}
return d3_selection(subgroups);
}
// TODO append(node)?
// TODO append(function)?
groups.append = function(name) {
name = d3.ns.qualify(name);
function append(node) {
return node.appendChild(document.createElement(name));
}
function appendNS(node) {
return node.appendChild(document.createElementNS(name.space, name.local));
}
return select(name.local ? appendNS : append);
};
// TODO insert(node, function)?
// TODO insert(function, string)?
// TODO insert(function, function)?
groups.insert = function(name, before) {
name = d3.ns.qualify(name);
function insert(node) {
return node.insertBefore(
document.createElement(name),
d3_select(before, node));
}
function insertNS(node) {
return node.insertBefore(
document.createElementNS(name.space, name.local),
d3_select(before, node));
}
return select(name.local ? insertNS : insert);
};
return groups;
}
function d3_selection_comparator(comparator) {
if (!arguments.length) comparator = d3.ascending;
return function(a, b) {
return comparator(a && a.__data__, b && b.__data__);
};
}
function d3_selection_enterNode(data) {
return {__data__: data};
}