Skip to content

Commit

Permalink
Add band/point invertExtent.
Browse files Browse the repository at this point in the history
  • Loading branch information
jheer committed Jun 8, 2016
1 parent c2f525a commit 27eab82
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 2 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,10 @@ Constructs a new band scale with the empty [domain](#band_domain), the unit [ran

Given a *value* in the input [domain](#band_domain), returns the start of the corresponding band derived from the output [range](#band_range). If the given *value* is not in the scale’s domain, returns undefined.

<a name="band_invertExtent" href="#band_invertExtent">#</a> <i>band</i>.<b>invertExtent</b>(<i>r0</i>[, <i>r1</i>])

Given a range of values from the [range](#band_range), returns an array of corresponding values from the [domain](#band_domain), respecting the scale [padding](#band_padding) and [bandwidth](#band_bandwidth). If two arguments *r0* and *r1* are provided, returns an array of the domain values contained within that range. If only a single argument *r0* is provided, returns an array containing the ordinal value under that point, if found. If no domain values are within the specified range, returns undefined.

<a name="band_domain" href="#band_domain">#</a> <i>band</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 band, the second domain value to the second band, and so on. Domain values are stored internally in a map from stringified value to index; the resulting index is then used to determine the band. Thus, a band scale’s values must be coercible to a string, and the stringified version of the domain value uniquely identifies the corresponding band. If *domain* is not specified, this method returns the current domain.
Expand Down Expand Up @@ -836,6 +840,10 @@ Constructs a new point scale with the empty [domain](#point_domain), the unit [r

Given a *value* in the input [domain](#point_domain), returns the corresponding point derived from the output [range](#point_range). If the given *value* is not in the scale’s domain, returns undefined.

<a name="point_invertExtent" href="#point_invertExtent">#</a> <i>point</i>.<b>invertExtent</b>(<i>r0</i>[, <i>r1</i>])

Given a range of values from the [range](#point_range), returns an array of corresponding values from the [domain](#point_domain), respecting the scale [padding](#point_padding) and [bandwidth](#point_bandwidth). If two arguments *r0* and *r1* are provided, returns an array of the domain values contained within that range. If only a single argument *r0* is provided, returns an array containing the ordinal value under that point, if found. If no domain values are within the specified range, returns undefined.

<a name="point_domain" href="#point_domain">#</a> <i>point</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 point, the second domain value to the second point, and so on. Domain values are stored internally in a map from stringified value to index; the resulting index is then used to determine the point. Thus, a point scale’s values must be coercible to a string, and the stringified version of the domain value uniquely identifies the corresponding point. If *domain* is not specified, this method returns the current domain.
Expand Down
26 changes: 24 additions & 2 deletions src/band.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {range as sequence} from "d3-array";
import {range as sequence, bisectRight} from "d3-array";
import ordinal from "./ordinal";

export default function band() {
Expand Down Expand Up @@ -69,6 +69,28 @@ export default function band() {
return arguments.length ? (align = Math.max(0, Math.min(1, _)), rescale()) : align;
};

scale.invertExtent = function(r0, r1) {
var lo = +r0,
hi = arguments.length > 1 ? +r1 : lo,
reverse = range[1] < range[0],
values = reverse ? ordinalRange().reverse() : ordinalRange(),
n = values.length - 1, a, b, t;

// order range inputs, bail if outside of scale range
if (hi < lo) t = lo, lo = hi, hi = t;
if (hi < values[0] || lo > range[1-reverse]) return undefined;

// binary search to index into scale range
a = Math.max(0, bisectRight(values, lo) - 1);
b = lo===hi ? a : bisectRight(values, hi) - 1;

// increment index a if lo is within padding gap
if (lo - values[a] > bandwidth + 1e-10) ++a;

if (reverse) t = a, a = n - b, b = n - t; // map + swap
return (a > b) ? undefined : domain().slice(a, b+1);
};

scale.copy = function() {
return band()
.domain(domain())
Expand Down Expand Up @@ -98,4 +120,4 @@ function pointish(scale) {

export function point() {
return pointish(band().paddingInner(1));
}
}
92 changes: 92 additions & 0 deletions test/band-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,4 +252,96 @@ tape("band.copy() isolates changes to the range", function(test) {
test.end();
});

tape("band.invertExtent(x) inverts single value", function(test) {
var s = scale.scaleBand().domain(["foo", "bar"]);

// ascending range
s.range([0,2]);
test.deepEqual(s.invertExtent(-1), undefined);
test.deepEqual(s.invertExtent(0.0), ["foo"]);
test.deepEqual(s.invertExtent(0.5), ["foo"]);
test.deepEqual(s.invertExtent(1.0), ["bar"]);
test.deepEqual(s.invertExtent(1.5), ["bar"]);
test.deepEqual(s.invertExtent(2.0), ["bar"]);
test.deepEqual(s.invertExtent(2.1), undefined);

// ascending range with padding
s.padding(0.3);
test.deepEqual(s.invertExtent(-1), undefined);
test.deepEqual(s.invertExtent(0.0), undefined);
test.deepEqual(s.invertExtent(0.5), ["foo"]);
test.deepEqual(s.invertExtent(1.0), undefined);
test.deepEqual(s.invertExtent(1.5), ["bar"]);
test.deepEqual(s.invertExtent(2.0), undefined);
test.deepEqual(s.invertExtent(2.1), undefined);

// descending range
s.padding(0).range([2, 0]);
test.deepEqual(s.invertExtent(-1), undefined);
test.deepEqual(s.invertExtent(0.0), ["bar"]);
test.deepEqual(s.invertExtent(0.5), ["bar"]);
test.deepEqual(s.invertExtent(1.0), ["foo"]);
test.deepEqual(s.invertExtent(1.5), ["foo"]);
test.deepEqual(s.invertExtent(2.0), ["foo"]);
test.deepEqual(s.invertExtent(2.1), undefined);

// descending range with padding
s.padding(0.3);
test.deepEqual(s.invertExtent(-1), undefined);
test.deepEqual(s.invertExtent(0.0), undefined);
test.deepEqual(s.invertExtent(0.5), ["bar"]);
test.deepEqual(s.invertExtent(1.0), undefined);
test.deepEqual(s.invertExtent(1.5), ["foo"]);
test.deepEqual(s.invertExtent(2.0), undefined);
test.deepEqual(s.invertExtent(2.1), undefined);

test.end();
});

tape("band.invertExtent(x, y) inverts value range", function(test) {
var s = scale.scaleBand().domain(["foo", "bar"]);

// ascending range
s.range([0, 2]);
test.deepEqual(s.invertExtent(-2, -1), undefined);
test.deepEqual(s.invertExtent(-1, 0), ["foo"]);
test.deepEqual(s.invertExtent(0, 0.5), ["foo"]);
test.deepEqual(s.invertExtent(0, 1), ["foo", "bar"]);
test.deepEqual(s.invertExtent(0, 2), ["foo", "bar"]);
test.deepEqual(s.invertExtent(2, 3), ["bar"]);
test.deepEqual(s.invertExtent(3, 4), undefined);

// ascending range with padding
s.padding(0.3);
test.deepEqual(s.invertExtent( -1, 0), undefined);
test.deepEqual(s.invertExtent(0.0, 0.1), undefined);
test.deepEqual(s.invertExtent(0.0, 0.5), ["foo"]);
test.deepEqual(s.invertExtent(0.5, 1.5), ["foo", "bar"]);
test.deepEqual(s.invertExtent(0.9, 1.1), undefined);
test.deepEqual(s.invertExtent(1.0, 1.5), ["bar"]);
test.deepEqual(s.invertExtent(1.9, 2.0), undefined);

// descending range
s.padding(0).range([2, 0]);
test.deepEqual(s.invertExtent(-2, -1), undefined);
test.deepEqual(s.invertExtent(-1, 0), ["bar"]);
test.deepEqual(s.invertExtent(0, 0.5), ["bar"]);
test.deepEqual(s.invertExtent(0, 1), ["foo", "bar"]);
test.deepEqual(s.invertExtent(0, 2), ["foo", "bar"]);
test.deepEqual(s.invertExtent(2, 3), ["foo"]);
test.deepEqual(s.invertExtent(3, 4), undefined);

// descending range with padding
s.padding(0.3);
test.deepEqual(s.invertExtent( -1, 0.0), undefined);
test.deepEqual(s.invertExtent(0.0, 0.1), undefined);
test.deepEqual(s.invertExtent(0.0, 0.5), ["bar"]);
test.deepEqual(s.invertExtent(0.5, 1.5), ["foo", "bar"]);
test.deepEqual(s.invertExtent(0.9, 1.1), undefined);
test.deepEqual(s.invertExtent(1.0, 1.5), ["foo"]);
test.deepEqual(s.invertExtent(1.9, 2.0), undefined);

test.end();
});

// TODO align tests for padding & round

0 comments on commit 27eab82

Please sign in to comment.