Skip to content

Commit 27eab82

Browse files
committed
Add band/point invertExtent.
1 parent c2f525a commit 27eab82

File tree

3 files changed

+124
-2
lines changed

3 files changed

+124
-2
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,10 @@ Constructs a new band scale with the empty [domain](#band_domain), the unit [ran
770770

771771
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.
772772

773+
<a name="band_invertExtent" href="#band_invertExtent">#</a> <i>band</i>.<b>invertExtent</b>(<i>r0</i>[, <i>r1</i>])
774+
775+
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.
776+
773777
<a name="band_domain" href="#band_domain">#</a> <i>band</i>.<b>domain</b>([<i>domain</i>])
774778

775779
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.
@@ -836,6 +840,10 @@ Constructs a new point scale with the empty [domain](#point_domain), the unit [r
836840

837841
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.
838842

843+
<a name="point_invertExtent" href="#point_invertExtent">#</a> <i>point</i>.<b>invertExtent</b>(<i>r0</i>[, <i>r1</i>])
844+
845+
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.
846+
839847
<a name="point_domain" href="#point_domain">#</a> <i>point</i>.<b>domain</b>([<i>domain</i>])
840848

841849
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.

src/band.js

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {range as sequence} from "d3-array";
1+
import {range as sequence, bisectRight} from "d3-array";
22
import ordinal from "./ordinal";
33

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

72+
scale.invertExtent = function(r0, r1) {
73+
var lo = +r0,
74+
hi = arguments.length > 1 ? +r1 : lo,
75+
reverse = range[1] < range[0],
76+
values = reverse ? ordinalRange().reverse() : ordinalRange(),
77+
n = values.length - 1, a, b, t;
78+
79+
// order range inputs, bail if outside of scale range
80+
if (hi < lo) t = lo, lo = hi, hi = t;
81+
if (hi < values[0] || lo > range[1-reverse]) return undefined;
82+
83+
// binary search to index into scale range
84+
a = Math.max(0, bisectRight(values, lo) - 1);
85+
b = lo===hi ? a : bisectRight(values, hi) - 1;
86+
87+
// increment index a if lo is within padding gap
88+
if (lo - values[a] > bandwidth + 1e-10) ++a;
89+
90+
if (reverse) t = a, a = n - b, b = n - t; // map + swap
91+
return (a > b) ? undefined : domain().slice(a, b+1);
92+
};
93+
7294
scale.copy = function() {
7395
return band()
7496
.domain(domain())
@@ -98,4 +120,4 @@ function pointish(scale) {
98120

99121
export function point() {
100122
return pointish(band().paddingInner(1));
101-
}
123+
}

test/band-test.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,4 +252,96 @@ tape("band.copy() isolates changes to the range", function(test) {
252252
test.end();
253253
});
254254

255+
tape("band.invertExtent(x) inverts single value", function(test) {
256+
var s = scale.scaleBand().domain(["foo", "bar"]);
257+
258+
// ascending range
259+
s.range([0,2]);
260+
test.deepEqual(s.invertExtent(-1), undefined);
261+
test.deepEqual(s.invertExtent(0.0), ["foo"]);
262+
test.deepEqual(s.invertExtent(0.5), ["foo"]);
263+
test.deepEqual(s.invertExtent(1.0), ["bar"]);
264+
test.deepEqual(s.invertExtent(1.5), ["bar"]);
265+
test.deepEqual(s.invertExtent(2.0), ["bar"]);
266+
test.deepEqual(s.invertExtent(2.1), undefined);
267+
268+
// ascending range with padding
269+
s.padding(0.3);
270+
test.deepEqual(s.invertExtent(-1), undefined);
271+
test.deepEqual(s.invertExtent(0.0), undefined);
272+
test.deepEqual(s.invertExtent(0.5), ["foo"]);
273+
test.deepEqual(s.invertExtent(1.0), undefined);
274+
test.deepEqual(s.invertExtent(1.5), ["bar"]);
275+
test.deepEqual(s.invertExtent(2.0), undefined);
276+
test.deepEqual(s.invertExtent(2.1), undefined);
277+
278+
// descending range
279+
s.padding(0).range([2, 0]);
280+
test.deepEqual(s.invertExtent(-1), undefined);
281+
test.deepEqual(s.invertExtent(0.0), ["bar"]);
282+
test.deepEqual(s.invertExtent(0.5), ["bar"]);
283+
test.deepEqual(s.invertExtent(1.0), ["foo"]);
284+
test.deepEqual(s.invertExtent(1.5), ["foo"]);
285+
test.deepEqual(s.invertExtent(2.0), ["foo"]);
286+
test.deepEqual(s.invertExtent(2.1), undefined);
287+
288+
// descending range with padding
289+
s.padding(0.3);
290+
test.deepEqual(s.invertExtent(-1), undefined);
291+
test.deepEqual(s.invertExtent(0.0), undefined);
292+
test.deepEqual(s.invertExtent(0.5), ["bar"]);
293+
test.deepEqual(s.invertExtent(1.0), undefined);
294+
test.deepEqual(s.invertExtent(1.5), ["foo"]);
295+
test.deepEqual(s.invertExtent(2.0), undefined);
296+
test.deepEqual(s.invertExtent(2.1), undefined);
297+
298+
test.end();
299+
});
300+
301+
tape("band.invertExtent(x, y) inverts value range", function(test) {
302+
var s = scale.scaleBand().domain(["foo", "bar"]);
303+
304+
// ascending range
305+
s.range([0, 2]);
306+
test.deepEqual(s.invertExtent(-2, -1), undefined);
307+
test.deepEqual(s.invertExtent(-1, 0), ["foo"]);
308+
test.deepEqual(s.invertExtent(0, 0.5), ["foo"]);
309+
test.deepEqual(s.invertExtent(0, 1), ["foo", "bar"]);
310+
test.deepEqual(s.invertExtent(0, 2), ["foo", "bar"]);
311+
test.deepEqual(s.invertExtent(2, 3), ["bar"]);
312+
test.deepEqual(s.invertExtent(3, 4), undefined);
313+
314+
// ascending range with padding
315+
s.padding(0.3);
316+
test.deepEqual(s.invertExtent( -1, 0), undefined);
317+
test.deepEqual(s.invertExtent(0.0, 0.1), undefined);
318+
test.deepEqual(s.invertExtent(0.0, 0.5), ["foo"]);
319+
test.deepEqual(s.invertExtent(0.5, 1.5), ["foo", "bar"]);
320+
test.deepEqual(s.invertExtent(0.9, 1.1), undefined);
321+
test.deepEqual(s.invertExtent(1.0, 1.5), ["bar"]);
322+
test.deepEqual(s.invertExtent(1.9, 2.0), undefined);
323+
324+
// descending range
325+
s.padding(0).range([2, 0]);
326+
test.deepEqual(s.invertExtent(-2, -1), undefined);
327+
test.deepEqual(s.invertExtent(-1, 0), ["bar"]);
328+
test.deepEqual(s.invertExtent(0, 0.5), ["bar"]);
329+
test.deepEqual(s.invertExtent(0, 1), ["foo", "bar"]);
330+
test.deepEqual(s.invertExtent(0, 2), ["foo", "bar"]);
331+
test.deepEqual(s.invertExtent(2, 3), ["foo"]);
332+
test.deepEqual(s.invertExtent(3, 4), undefined);
333+
334+
// descending range with padding
335+
s.padding(0.3);
336+
test.deepEqual(s.invertExtent( -1, 0.0), undefined);
337+
test.deepEqual(s.invertExtent(0.0, 0.1), undefined);
338+
test.deepEqual(s.invertExtent(0.0, 0.5), ["bar"]);
339+
test.deepEqual(s.invertExtent(0.5, 1.5), ["foo", "bar"]);
340+
test.deepEqual(s.invertExtent(0.9, 1.1), undefined);
341+
test.deepEqual(s.invertExtent(1.0, 1.5), ["foo"]);
342+
test.deepEqual(s.invertExtent(1.9, 2.0), undefined);
343+
344+
test.end();
345+
});
346+
255347
// TODO align tests for padding & round

0 commit comments

Comments
 (0)