Skip to content

Commit

Permalink
Multi-value map support for classed.
Browse files Browse the repository at this point in the history
  • Loading branch information
mbostock committed Aug 5, 2012
1 parent a39ca24 commit f2b45c1
Show file tree
Hide file tree
Showing 8 changed files with 241 additions and 135 deletions.
84 changes: 50 additions & 34 deletions d3.v2.js
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@
return d == null;
}
function d3_collapse(s) {
return s.replace(/^\s+|\s+$/g, "").replace(/\s+/g, " ");
return s.trim().replace(/\s+/g, " ");
}
d3.range = function(start, stop, step) {
if (arguments.length < 3) {
Expand Down Expand Up @@ -1430,45 +1430,61 @@
return value == null ? name.local ? attrNullNS : attrNull : typeof value === "function" ? name.local ? attrFunctionNS : attrFunction : name.local ? attrConstantNS : attrConstant;
}
d3_selectionPrototype.classed = function(name, value) {
var names = d3_collapse(name).split(" "), n = names.length, i = -1;
if (arguments.length > 1) {
while (++i < n) d3_selection_classed.call(this, names[i], value);
if (arguments.length < 2) {
if ((value = typeof name) === "function") {
return this.each(function() {
var x = name.apply(this, arguments);
for (value in x) d3_selection_classed(value, x[value]).apply(this, arguments);
});
}
if (value === "string") {
var node = this.node(), n = (name = name.trim().split(/^|\s+/g)).length, i = -1;
if (value = node.classList) {
while (++i < n) if (!value.contains(name[i])) return false;
} else {
value = node.className;
if (value.baseVal != null) value = value.baseVal;
while (++i < n) if (!d3_selection_classedRe(name[i]).test(value)) return false;
}
return true;
}
for (value in name) this.each(d3_selection_classed(value, name[value]));
return this;
} else {
while (++i < n) if (!d3_selection_classed.call(this, names[i])) return false;
return true;
}
return this.each(d3_selection_classed(name, value));
};
function d3_selection_classedRe(name) {
return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g");
}
function d3_selection_classed(name, value) {
var re = new RegExp("(^|\\s+)" + d3.requote(name) + "(\\s+|$)", "g");
if (arguments.length < 2) {
var node = this.node();
if (c = node.classList) return c.contains(name);
var c = node.className;
re.lastIndex = 0;
return re.test(c.baseVal != null ? c.baseVal : c);
}
function classedAdd() {
if (c = this.classList) return c.add(name);
var c = this.className, cb = c.baseVal != null, cv = cb ? c.baseVal : c;
re.lastIndex = 0;
if (!re.test(cv)) {
cv = d3_collapse(cv + " " + name);
if (cb) c.baseVal = cv; else this.className = cv;
}
}
function classedRemove() {
if (c = this.classList) return c.remove(name);
var c = this.className, cb = c.baseVal != null, cv = cb ? c.baseVal : c;
if (cv) {
cv = d3_collapse(cv.replace(re, " "));
if (cb) c.baseVal = cv; else this.className = cv;
}
name = name.trim().split(/\s+/).map(d3_selection_classedName);
var n = name.length;
function classedConstant() {
var i = -1;
while (++i < n) name[i](this, value);
}
function classedFunction() {
(value.apply(this, arguments) ? classedAdd : classedRemove).call(this);
}
return this.each(typeof value === "function" ? classedFunction : value ? classedAdd : classedRemove);
var i = -1, x = value.apply(this, arguments);
while (++i < n) name[i](this, x);
}
return typeof value === "function" ? classedFunction : classedConstant;
}
function d3_selection_classedName(name) {
var re = d3_selection_classedRe(name);
return function(node, value) {
if (c = node.classList) return value ? c.add(name) : c.remove(name);
var c = node.className, cb = c.baseVal != null, cv = cb ? c.baseVal : c;
if (value) {
re.lastIndex = 0;
if (!re.test(cv)) {
cv = d3_collapse(cv + " " + name);
if (cb) c.baseVal = cv; else node.className = cv;
}
} else if (cv) {
cv = d3_collapse(cv.replace(re, " "));
if (cb) c.baseVal = cv; else node.className = cv;
}
};
}
d3_selectionPrototype.style = function(name, value, priority) {
var n = arguments.length;
Expand Down
8 changes: 4 additions & 4 deletions d3.v2.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/core/collapse.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
function d3_collapse(s) {
return s.replace(/^\s+|\s+$/g, "").replace(/\s+/g, " ");
return s.trim().replace(/\s+/g, " ");
}
6 changes: 3 additions & 3 deletions src/core/selection-attr.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ d3_selectionPrototype.attr = function(name, value) {

// For attr(function), the function must return an object for each element,
// specifying the names and values of the attributes to set or remove. The
// values must be constants, not function.
// values must be constants, not functions.
if ((value = typeof name) === "function") {
return this.each(function() {
var x = name.apply(this, arguments);
Expand All @@ -19,8 +19,8 @@ d3_selectionPrototype.attr = function(name, value) {
: value.getAttribute(name);
}

// Otherwise, for attr(object), the object specifies the names and values of
// the attributes to set or remove. The values may be functions that are
// For attr(object), the object specifies the names and values of the
// attributes to set or remove. The values may be functions that are
// evaluated for each element.
for (value in name) this.each(d3_selection_attr(value, name[value]));
return this;
Expand Down
114 changes: 70 additions & 44 deletions src/core/selection-classed.js
Original file line number Diff line number Diff line change
@@ -1,61 +1,87 @@
d3_selectionPrototype.classed = function(name, value) {
var names = d3_collapse(name).split(" "),
n = names.length,
i = -1;
if (arguments.length > 1) {
while (++i < n) d3_selection_classed.call(this, names[i], value);
if (arguments.length < 2) {

// For classed(function), the function must return an object for each
// element, specifying the names of classes to add or remove. The values
// must be constants, not functions.
if ((value = typeof name) === "function") {
return this.each(function() {
var x = name.apply(this, arguments);
for (value in x) d3_selection_classed(value, x[value]).apply(this, arguments);
});
}

// For classed(string), return true only if the first node has the specified
// class or classes. Note that even if the browser supports DOMTokenList, it
// probably doesn't support it on SVG elements (which can be animated).
if (value === "string") {
var node = this.node(),
n = (name = name.trim().split(/^|\s+/g)).length,
i = -1;
if (value = node.classList) {
while (++i < n) if (!value.contains(name[i])) return false;
} else {
value = node.className;
if (value.baseVal != null) value = value.baseVal;
while (++i < n) if (!d3_selection_classedRe(name[i]).test(value)) return false;
}
return true;
}

// For classed(object), the object specifies the names of classes to add or
// remove. The values may be functions that are evaluated for each element.
for (value in name) this.each(d3_selection_classed(value, name[value]));
return this;
} else {
while (++i < n) if (!d3_selection_classed.call(this, names[i])) return false;
return true;
}

// Otherwise, both a name and a value are specified, and are handled as below.
return this.each(d3_selection_classed(name, value));
};

function d3_selection_classedRe(name) {
return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g");
}

// Multiple class names are allowed (e.g., "foo bar").
function d3_selection_classed(name, value) {
var re = new RegExp("(^|\\s+)" + d3.requote(name) + "(\\s+|$)", "g");
name = name.trim().split(/\s+/).map(d3_selection_classedName);
var n = name.length;

// If no value is specified, return the first value.
if (arguments.length < 2) {
var node = this.node();
if (c = node.classList) return c.contains(name);
var c = node.className;
re.lastIndex = 0;
return re.test(c.baseVal != null ? c.baseVal : c);
function classedConstant() {
var i = -1;
while (++i < n) name[i](this, value);
}

function classedAdd() {
if (c = this.classList) return c.add(name);
var c = this.className,
cb = c.baseVal != null,
cv = cb ? c.baseVal : c;
re.lastIndex = 0;
if (!re.test(cv)) {
cv = d3_collapse(cv + " " + name);
if (cb) c.baseVal = cv;
else this.className = cv;
}
// When the value is a function, the function is still evaluated only once per
// element even if there are multiple class names.
function classedFunction() {
var i = -1, x = value.apply(this, arguments);
while (++i < n) name[i](this, x);
}

function classedRemove() {
if (c = this.classList) return c.remove(name);
var c = this.className,
return typeof value === "function"
? classedFunction
: classedConstant;
}

function d3_selection_classedName(name) {
var re = d3_selection_classedRe(name);
return function(node, value) {
if (c = node.classList) return value ? c.add(name) : c.remove(name);
var c = node.className,
cb = c.baseVal != null,
cv = cb ? c.baseVal : c;
if (cv) {
if (value) {
re.lastIndex = 0;
if (!re.test(cv)) {
cv = d3_collapse(cv + " " + name);
if (cb) c.baseVal = cv;
else node.className = cv;
}
} else if (cv) {
cv = d3_collapse(cv.replace(re, " "));
if (cb) c.baseVal = cv;
else this.className = cv;
else node.className = cv;
}
}

function classedFunction() {
(value.apply(this, arguments)
? classedAdd
: classedRemove).call(this);
}

return this.each(typeof value === "function"
? classedFunction : value
? classedAdd
: classedRemove);
};
}
8 changes: 4 additions & 4 deletions src/core/selection-property.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ d3_selectionPrototype.property = function(name, value) {

// For property(function), the function must return an object for each
// element, specifying the names and values of the properties to set or
// remove. The values must be constants, not function.
// remove. The values must be constants, not functions.
if ((value = typeof name) === "function") {
return this.each(function() {
var x = name.apply(this, arguments);
Expand All @@ -14,9 +14,9 @@ d3_selectionPrototype.property = function(name, value) {
// For property(string), return the property value for the first node.
if (value === "string") return this.node()[name];

// Otherwise, for property(object), the object specifies the names and
// values of the properties to set or remove. The values may be functions
// that are evaluated for each element.
// For property(object), the object specifies the names and values of the
// properties to set or remove. The values may be functions that are
// evaluated for each element.
for (value in name) this.each(d3_selection_property(value, name[value]));
return this;
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/selection-style.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ d3_selectionPrototype.style = function(name, value, priority) {

// For style(function) or style(function, priority), the function must
// return an object for each element, specifying the names and values of the
// styles to set or remove. The values must be constants, not function.
// styles to set or remove. The values must be constants, not functions.
if ((priority = typeof name) === "function") {
if (n < 2) value = "";
return this.each(function() {
Expand Down
Loading

0 comments on commit f2b45c1

Please sign in to comment.