From 67c7a58e1140b4088ab99872c809e19c32f94fc5 Mon Sep 17 00:00:00 2001 From: Jeffrey Heer Date: Tue, 7 Jun 2016 13:17:31 -0700 Subject: [PATCH] Add ordinal invert. --- README.md | 13 +++++++++++++ src/ordinal.js | 20 +++++++++++++++----- test/ordinal-test.js | 10 ++++++++++ 3 files changed, 38 insertions(+), 5 deletions(-) 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/ordinal.js b/src/ordinal.js index aede376..d5678f1 100644 --- a/src/ordinal.js +++ b/src/ordinal.js @@ -3,25 +3,26 @@ import {slice} from "./array"; export var implicit = {name: "implicit"}; -export default function ordinal(range) { +export default function ordinal() { var index = map(), + inverseIndex = null, domain = [], + range = [], unknown = implicit; - range = range == null ? [] : slice.call(range); - function scale(d) { var key = d + "", i = index.get(key); 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 +36,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 +53,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