Multi-value map support for classed.
This commit is contained in:
Родитель
a39ca24fb8
Коммит
f2b45c132d
|
@ -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) {
|
||||
|
@ -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);
|
||||
return this;
|
||||
} else {
|
||||
while (++i < n) if (!d3_selection_classed.call(this, names[i])) return false;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
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;
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
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;
|
||||
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;
|
||||
}
|
||||
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) {
|
||||
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);
|
||||
var i = -1, x = value.apply(this, arguments);
|
||||
while (++i < n) name[i](this, x);
|
||||
}
|
||||
return this.each(typeof value === "function" ? classedFunction : value ? classedAdd : classedRemove);
|
||||
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;
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -1,3 +1,3 @@
|
|||
function d3_collapse(s) {
|
||||
return s.replace(/^\s+|\s+$/g, "").replace(/\s+/g, " ");
|
||||
return s.trim().replace(/\s+/g, " ");
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -17,13 +17,6 @@ suite.addBatch({
|
|||
body.classed("bar", true);
|
||||
assert.equal(document.body.className, "foo bar");
|
||||
},
|
||||
"adds a missing class as a function": function(body) {
|
||||
body.attr("class", null);
|
||||
body.classed("foo", function() { return true; });
|
||||
assert.equal(document.body.className, "foo");
|
||||
body.classed("bar", function() { return true; });
|
||||
assert.equal(document.body.className, "foo bar");
|
||||
},
|
||||
"removes an existing class as false": function(body) {
|
||||
body.attr("class", "bar foo");
|
||||
body.classed("foo", false);
|
||||
|
@ -31,13 +24,6 @@ suite.addBatch({
|
|||
body.classed("bar", false);
|
||||
assert.equal(document.body.className, "");
|
||||
},
|
||||
"removes an existing class as a function": function(body) {
|
||||
body.attr("class", "bar foo");
|
||||
body.classed("foo", function() { return false; });
|
||||
assert.equal(document.body.className, "bar");
|
||||
body.classed("bar", function() { return false; });
|
||||
assert.equal(document.body.className, "");
|
||||
},
|
||||
"preserves an existing class as true": function(body) {
|
||||
body.attr("class", "bar foo");
|
||||
body.classed("foo", true);
|
||||
|
@ -45,13 +31,6 @@ suite.addBatch({
|
|||
body.classed("bar", true);
|
||||
assert.equal(document.body.className, "bar foo");
|
||||
},
|
||||
"preserves an existing class as a function": function(body) {
|
||||
body.attr("class", "bar foo");
|
||||
body.classed("foo", function() { return true; });
|
||||
assert.equal(document.body.className, "bar foo");
|
||||
body.classed("bar", function() { return true; });
|
||||
assert.equal(document.body.className, "bar foo");
|
||||
},
|
||||
"preserves a missing class as false": function(body) {
|
||||
body.attr("class", "baz");
|
||||
body.classed("foo", false);
|
||||
|
@ -60,14 +39,6 @@ suite.addBatch({
|
|||
body.classed("bar", false);
|
||||
assert.equal(document.body.className, "");
|
||||
},
|
||||
"preserves a missing class as a function": function(body) {
|
||||
body.attr("class", "baz");
|
||||
body.classed("foo", function() { return false; });
|
||||
assert.equal(document.body.className, "baz");
|
||||
body.attr("class", null);
|
||||
body.classed("bar", function() { return false; });
|
||||
assert.equal(document.body.className, "");
|
||||
},
|
||||
"gets an existing class": function(body) {
|
||||
body.attr("class", " foo\tbar baz");
|
||||
assert.isTrue(body.classed("foo"));
|
||||
|
@ -80,27 +51,120 @@ suite.addBatch({
|
|||
assert.isFalse(body.classed("bare"));
|
||||
assert.isFalse(body.classed("rbaz"));
|
||||
},
|
||||
"returns the current selection": function(body) {
|
||||
assert.isTrue(body.classed("foo", true) === body);
|
||||
"accepts a name with whitespace, collapsing it": function(body) {
|
||||
body.attr("class", null);
|
||||
body.classed(" foo\t", true);
|
||||
assert.equal(document.body.className, "foo");
|
||||
body.classed("\tfoo ", false);
|
||||
assert.equal(document.body.className, "");
|
||||
},
|
||||
"adds missing classes as true": function(body) {
|
||||
"accepts a name with multiple classes separated by whitespace": function(body) {
|
||||
body.attr("class", null);
|
||||
body.classed("foo bar", true);
|
||||
assert.equal(document.body.className, "foo bar");
|
||||
},
|
||||
"gets existing classes": function(body) {
|
||||
body.attr("class", " foo\tbar baz");
|
||||
assert.isTrue(body.classed("foo"));
|
||||
assert.isTrue(body.classed("foo bar"));
|
||||
assert.isTrue(body.classed("bar baz"));
|
||||
assert.isTrue(body.classed("foo bar baz"));
|
||||
},
|
||||
"does not get missing classes": function(body) {
|
||||
body.attr("class", " foo\tbar baz");
|
||||
assert.isFalse(body.classed("foob"));
|
||||
assert.isTrue(body.classed("bar foo"));
|
||||
assert.isFalse(body.classed("foo bar baz"));
|
||||
assert.isFalse(body.classed("foob bar"));
|
||||
assert.isFalse(body.classed("bar baz blah"));
|
||||
assert.isFalse(body.classed("foo bar baz moo"));
|
||||
body.classed("bar foo", false);
|
||||
assert.equal(document.body.className, "");
|
||||
},
|
||||
"accepts a silly class name with unsafe characters": function(body) {
|
||||
body.attr("class", null);
|
||||
body.classed("foo.bar", true);
|
||||
assert.equal(document.body.className, "foo.bar");
|
||||
assert.isTrue(body.classed("foo.bar"));
|
||||
assert.isFalse(body.classed("foo"));
|
||||
assert.isFalse(body.classed("bar"));
|
||||
body.classed("bar.foo", false);
|
||||
assert.equal(document.body.className, "foo.bar");
|
||||
body.classed("foo.bar", false);
|
||||
assert.equal(document.body.className, "");
|
||||
},
|
||||
"accepts a name with duplicate classes, ignoring them": function(body) {
|
||||
body.attr("class", null);
|
||||
body.classed(" foo\tfoo ", true);
|
||||
assert.equal(document.body.className, "foo");
|
||||
body.classed("\tfoo foo ", false);
|
||||
assert.equal(document.body.className, "");
|
||||
},
|
||||
"accepts a value function returning true or false": function(body) {
|
||||
body.attr("class", null);
|
||||
body.classed("foo", function() { return true; });
|
||||
assert.equal(document.body.className, "foo");
|
||||
body.classed("foo bar", function() { return true; });
|
||||
assert.equal(document.body.className, "foo bar");
|
||||
body.classed("foo", function() { return false; });
|
||||
assert.equal(document.body.className, "bar");
|
||||
},
|
||||
"accepts a value function returning returning an object containing true or false": function(body) {
|
||||
body.attr("class", null);
|
||||
body.classed(function() { return {foo: true}; });
|
||||
assert.equal(document.body.className, "foo");
|
||||
body.classed(function() { return {foo: true, bar: true}; });
|
||||
assert.equal(document.body.className, "foo bar");
|
||||
body.classed(function() { return {bar: false, foo: false}; });
|
||||
assert.equal(document.body.className, "");
|
||||
},
|
||||
"accepts a name object containing true or false": function(body) {
|
||||
body.attr("class", null);
|
||||
body.classed({foo: true});
|
||||
assert.equal(document.body.className, "foo");
|
||||
body.classed({bar: true, foo: false});
|
||||
assert.equal(document.body.className, "bar");
|
||||
},
|
||||
"accepts a name object containing a function returning true or false": function(body) {
|
||||
body.attr("class", null);
|
||||
body.classed({foo: function() { return true; }});
|
||||
assert.equal(document.body.className, "foo");
|
||||
},
|
||||
"accepts a name object containing a mix of functions and non-functions": function(body) {
|
||||
body.attr("class", "foo");
|
||||
body.classed({foo: false, bar: function() { return true; }});
|
||||
assert.equal(document.body.className, "bar");
|
||||
},
|
||||
"the value may be truthy or falsey": function(body) {
|
||||
body.attr("class", "foo");
|
||||
body.classed({foo: null, bar: function() { return 1; }});
|
||||
assert.equal(document.body.className, "bar");
|
||||
},
|
||||
"keys in the name object may contain whitespace": function(body) {
|
||||
body.attr("class", null);
|
||||
body.classed({" foo\t": function() { return true; }});
|
||||
assert.equal(document.body.className, "foo");
|
||||
body.attr("class", null);
|
||||
body.classed(function() { return {"\tfoo ": true}; });
|
||||
assert.equal(document.body.className, "foo");
|
||||
},
|
||||
"keys in the name object may reference multiple classes": function(body) {
|
||||
body.attr("class", null);
|
||||
body.classed({"foo bar": function() { return true; }});
|
||||
assert.equal(document.body.className, "foo bar");
|
||||
body.attr("class", null);
|
||||
body.classed(function() { return {"foo bar": true}; });
|
||||
assert.equal(document.body.className, "foo bar");
|
||||
},
|
||||
"keys in the name object may contain duplicates": function(body) {
|
||||
body.attr("class", null);
|
||||
body.classed({"foo foo": function() { return true; }});
|
||||
assert.equal(document.body.className, "foo");
|
||||
body.attr("class", null);
|
||||
body.classed(function() { return {"foo foo": true}; });
|
||||
assert.equal(document.body.className, "foo");
|
||||
},
|
||||
"value functions are only evaluated once when used for multiple classes": function(body) {
|
||||
var count = 0;
|
||||
body.attr("class", null);
|
||||
body.classed({"foo bar": function() { return ++count; }});
|
||||
assert.equal(document.body.className, "foo bar");
|
||||
assert.equal(count, 1);
|
||||
body.attr("class", null);
|
||||
body.classed(function() { return {"foo bar": count--}; });
|
||||
assert.equal(document.body.className, "foo bar");
|
||||
assert.equal(count, 0);
|
||||
},
|
||||
"returns the current selection": function(body) {
|
||||
assert.isTrue(body.classed("foo", true) === body);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче