Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support ordinal scale inversion. #60

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Expand Up @@ -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.

<a name="ordinal_invert" href="#ordinal_invert">#</a> <i>ordinal</i>.<b>invert</b>(<i>value</i>)

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"
```

<a name="ordinal_domain" href="#ordinal_domain">#</a> <i>ordinal</i>.<b>domain</b>([<i>domain</i>])

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.
Expand Down
1 change: 1 addition & 0 deletions src/band.js
Expand Up @@ -14,6 +14,7 @@ export default function band() {
align = 0.5;

delete scale.unknown;
delete scale.invert;

function rescale() {
var n = domain().length,
Expand Down
15 changes: 13 additions & 2 deletions src/ordinal.js
Expand Up @@ -5,6 +5,7 @@ export var implicit = {name: "implicit"};

export default function ordinal(range) {
var index = map(),
inverseIndex = null,
domain = [],
unknown = implicit;

Expand All @@ -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;
Expand All @@ -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)
Expand All @@ -43,4 +54,4 @@ export default function ordinal(range) {
};

return scale;
}
}
10 changes: 10 additions & 0 deletions test/ordinal-test.js
Expand Up @@ -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();
});