diff --git a/README.md b/README.md
index 17a3442..100fc0b 100644
--- a/README.md
+++ b/README.md
@@ -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.
+# band.invertExtent(r0[, r1])
+
+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.
+
# band.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 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
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.
+# point.invertExtent(r0[, r1])
+
+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.
+
# point.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 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.
diff --git a/src/band.js b/src/band.js
index 05acdde..195116a 100644
--- a/src/band.js
+++ b/src/band.js
@@ -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() {
@@ -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())
@@ -98,4 +120,4 @@ function pointish(scale) {
export function point() {
return pointish(band().paddingInner(1));
-}
+}
\ No newline at end of file
diff --git a/test/band-test.js b/test/band-test.js
index 05d6c9f..1c35df0 100644
--- a/test/band-test.js
+++ b/test/band-test.js
@@ -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