diff --git a/README.md b/README.md
index 21ab262..8693fd1 100644
--- a/README.md
+++ b/README.md
@@ -734,6 +734,19 @@ Constructs a new ordinal scale with an empty [domain](#ordinal_domain) and the s
Given a *value* in the input [domain](#ordinal_domain), returns the corresponding value in the output [range](#ordinal_range). If the given *value* is not in the scale’s [domain](#ordinal_domain), returns the [unknown](#ordinal_value); or, if the unknown value is [implicit](#implicit) (the default), then the *value* is implicitly added to the domain and the next-available value in the range is assigned to *value*, such that this and subsequent invocations of the scale given the same input *value* return the same output value.
+# ordinal.invert(value)
+
+Given a *value* from the [range](#ordinal_range), returns the first corresponding value from the [domain](#ordinal_domain). If the range includes duplicate values, this method returns the corresponding value with the lowest index in the domain array. For example:
+
+```js
+var ordinal = d3.scaleOrdinal()
+ .domain(["a", "b", "c"])
+ .range(["red", "white", "red"]);
+
+ordinal.invert("red"); // "a"
+ordinal.invert("white"); // "b"
+```
+
# ordinal.domain([domain])
If *domain* is specified, sets the domain to the specified array of values. The first element in *domain* will be mapped to the first element in the range, the second domain value to the second range value, and so on. Domain values are stored internally in a map from stringified value to index; the resulting index is then used to retrieve a value from the range. Thus, an ordinal scale’s values must be coercible to a string, and the stringified version of the domain value uniquely identifies the corresponding range value. If *domain* is not specified, this method returns the current domain.
diff --git a/src/band.js b/src/band.js
index 05acdde..abecef2 100644
--- a/src/band.js
+++ b/src/band.js
@@ -14,6 +14,7 @@ export default function band() {
align = 0.5;
delete scale.unknown;
+ delete scale.invert;
function rescale() {
var n = domain().length,
diff --git a/src/ordinal.js b/src/ordinal.js
index aede376..bf54c57 100644
--- a/src/ordinal.js
+++ b/src/ordinal.js
@@ -5,6 +5,7 @@ export var implicit = {name: "implicit"};
export default function ordinal(range) {
var index = map(),
+ inverseIndex = null,
domain = [],
unknown = implicit;
@@ -15,13 +16,14 @@ export default function ordinal(range) {
if (!i) {
if (unknown !== implicit) return unknown;
index.set(key, i = domain.push(d));
+ inverseIndex = null;
}
return range[(i - 1) % range.length];
}
scale.domain = function(_) {
if (!arguments.length) return domain.slice();
- domain = [], index = map();
+ domain = [], index = map(), inverseIndex = null;
var i = -1, n = _.length, d, key;
while (++i < n) if (!index.has(key = (d = _[i]) + "")) index.set(key, domain.push(d));
return scale;
@@ -35,6 +37,15 @@ export default function ordinal(range) {
return arguments.length ? (unknown = _, scale) : unknown;
};
+ scale.invert = function(_) {
+ if (!inverseIndex) {
+ inverseIndex = map();
+ var n = domain.length;
+ while (--n >= 0) inverseIndex.set(range[n], n+1);
+ }
+ return domain[inverseIndex.get(_) - 1];
+ };
+
scale.copy = function() {
return ordinal()
.domain(domain)
@@ -43,4 +54,4 @@ export default function ordinal(range) {
};
return scale;
-}
+}
\ No newline at end of file
diff --git a/test/ordinal-test.js b/test/ordinal-test.js
index cc9ccf6..424602b 100644
--- a/test/ordinal-test.js
+++ b/test/ordinal-test.js
@@ -202,3 +202,13 @@ tape("ordinal.copy() changes to the range are isolated", function(test) {
test.deepEqual(s2.range(), ["foo", "baz"]);
test.end();
});
+
+tape("ordinal.invert(x) returns first matching domain value", function(test) {
+ var s = scale.scaleOrdinal().domain(["foo", "bar", "baz", "oof"]).range(["a", "b", "", "a"]);
+ test.equal(s.invert(), undefined);
+ test.equal(s.invert("a"), "foo");
+ test.equal(s.invert("b"), "bar");
+ test.equal(s.invert(""), "baz");
+ test.equal(s.invert("c"), undefined);
+ test.end();
+});
\ No newline at end of file