Skip to content

Commit 532bf71

Browse files
committed
Brushing for ordinal scales.
Since the ordinal scale's domain is not continuous, the brush extent is reported in the range rather than in the domain for ordinal scales. We'll leave it to the caller to interpret this as desired.
1 parent 552622d commit 532bf71

6 files changed

Lines changed: 134 additions & 26 deletions

File tree

d3.js

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2325,6 +2325,10 @@ function d3_scaleExtent(domain) {
23252325
var start = domain[0], stop = domain[domain.length - 1];
23262326
return start < stop ? [start, stop] : [stop, start];
23272327
}
2328+
2329+
function d3_scaleRange(scale) {
2330+
return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range());
2331+
}
23282332
function d3_scale_nice(domain, nice) {
23292333
var i0 = 0,
23302334
i1 = domain.length - 1,
@@ -3814,7 +3818,7 @@ d3.svg.axis = function() {
38143818
tickTransform;
38153819

38163820
// Domain.
3817-
var range = scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range()),
3821+
var range = d3_scaleRange(scale),
38183822
path = g.selectAll(".domain").data([0]),
38193823
pathEnter = path.enter().append("svg:path").attr("class", "domain"),
38203824
pathUpdate = transition(path);
@@ -4012,12 +4016,12 @@ d3.svg.brush = function() {
40124016
// Initialize the background to fill the defined range.
40134017
// If the range isn't defined, you can post-process.
40144018
if (x) {
4015-
e = d3_scaleExtent(x.range());
4019+
e = d3_scaleRange(x);
40164020
bg.attr("x", e[0]).attr("width", e[1] - e[0]);
40174021
d3_svg_brushRedrawX(g, extent);
40184022
}
40194023
if (y) {
4020-
e = d3_scaleExtent(y.range());
4024+
e = d3_scaleRange(y);
40214025
bg.attr("y", e[0]).attr("height", e[1] - e[0]);
40224026
d3_svg_brushRedrawY(g, extent);
40234027
}
@@ -4094,11 +4098,13 @@ d3.svg.brush = function() {
40944098
// Invert the pixel extent to data-space.
40954099
if (!arguments.length) {
40964100
if (x) {
4097-
x0 = x.invert(extent[0][0]), x1 = x.invert(extent[1][0]);
4101+
x0 = extent[0][0], x1 = extent[1][0];
4102+
if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1);
40984103
if (x1 < x0) t = x0, x0 = x1, x1 = t;
40994104
}
41004105
if (y) {
4101-
y0 = y.invert(extent[0][1]), y1 = y.invert(extent[1][1]);
4106+
y0 = extent[0][1], y1 = extent[1][1];
4107+
if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1);
41024108
if (y1 < y0) t = y0, y0 = y1, y1 = t;
41034109
}
41044110
return x && y ? [[x0, y0], [x1, y1]] : x ? [x0, x1] : y && [y0, y1];
@@ -4108,14 +4114,14 @@ d3.svg.brush = function() {
41084114
if (x) {
41094115
x0 = z[0], x1 = z[1];
41104116
if (y) x0 = x0[0], x1 = x1[0];
4111-
x0 = x(x0), x1 = x(x1);
4117+
if (x.invert) x0 = x(x0), x1 = x(x1);
41124118
if (x1 < x0) t = x0, x0 = x1, x1 = t;
41134119
extent[0][0] = x0, extent[1][0] = x1;
41144120
}
41154121
if (y) {
41164122
y0 = z[0], y1 = z[1];
41174123
if (x) y0 = y0[1], y1 = y1[1];
4118-
y0 = y(y0), y1 = y(y1);
4124+
if (y.invert) y0 = y(y0), y1 = y(y1);
41194125
if (y1 < y0) t = y0, y0 = y1, y1 = t;
41204126
extent[0][1] = y0, extent[1][1] = y1;
41214127
}
@@ -4230,28 +4236,30 @@ function d3_svg_brushMove() {
42304236
}
42314237

42324238
function d3_svg_brushMove1(mouse, scale, i) {
4233-
var range = d3_scaleExtent(scale.range()),
4239+
var range = d3_scaleRange(scale),
4240+
r0 = range[0],
4241+
r1 = range[1],
42344242
offset = d3_svg_brushOffset[i],
42354243
size = d3_svg_brushExtent[1][i] - d3_svg_brushExtent[0][i],
42364244
min,
42374245
max;
42384246

42394247
// When dragging, reduce the range by the extent size and offset.
42404248
if (d3_svg_brushDrag) {
4241-
range[0] -= offset;
4242-
range[1] -= size + offset;
4249+
r0 -= offset;
4250+
r1 -= size + offset;
42434251
}
42444252

42454253
// Clamp the mouse so that the extent fits within the range extent.
4246-
min = Math.max(range[0], Math.min(range[1], mouse[i]));
4254+
min = Math.max(r0, Math.min(r1, mouse[i]));
42474255

42484256
// Compute the new extent bounds.
42494257
if (d3_svg_brushDrag) {
42504258
max = (min += offset) + size;
42514259
} else {
42524260

42534261
// If the ALT key is pressed, then preserve the center of the extent.
4254-
if (d3_svg_brushCenter) offset = Math.max(range[0], Math.min(range[1], 2 * d3_svg_brushCenter[i] - min));
4262+
if (d3_svg_brushCenter) offset = Math.max(r0, Math.min(r1, 2 * d3_svg_brushCenter[i] - min));
42554263

42564264
// Compute the min and max of the offset and mouse.
42574265
if (offset < min) {

d3.min.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/brush/brush-ordinal.html

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
5+
<title>Brush</title>
6+
<script type="text/javascript" src="../../d3.js"></script>
7+
<style type="text/css">
8+
9+
svg {
10+
font: 10px sans-serif;
11+
}
12+
13+
path {
14+
-webkit-transition: fill-opacity 250ms linear;
15+
}
16+
17+
.selecting path {
18+
fill-opacity: .2;
19+
}
20+
21+
.selecting path.selected {
22+
stroke: #f00;
23+
stroke-width: 2px;
24+
}
25+
26+
.axis path, .axis line {
27+
fill: none;
28+
stroke: #000;
29+
shape-rendering: crispEdges;
30+
}
31+
32+
.brush .extent {
33+
stroke: #fff;
34+
fill-opacity: .125;
35+
shape-rendering: crispEdges;
36+
}
37+
38+
</style>
39+
</head>
40+
<body>
41+
<script type="text/javascript">
42+
43+
var data = d3.svg.symbolTypes;
44+
45+
var m = [10, 10, 20, 10],
46+
w = 960 - m[1] - m[3],
47+
h = 100 - m[0] - m[2];
48+
49+
var x = d3.scale.ordinal().domain(data).rangePoints([0, w], 1);
50+
51+
var svg = d3.select("body").append("svg:svg")
52+
.attr("width", w + m[1] + m[3])
53+
.attr("height", h + m[0] + m[2])
54+
.append("svg:g")
55+
.attr("transform", "translate(" + m[3] + "," + m[0] + ")");
56+
57+
svg.append("svg:g")
58+
.attr("class", "x axis")
59+
.attr("transform", "translate(0," + h + ")")
60+
.call(d3.svg.axis().scale(x).orient("bottom"));
61+
62+
var symbol = svg.append("svg:g").selectAll("path")
63+
.data(data)
64+
.enter().append("svg:path")
65+
.attr("transform", function(d) { return "translate(" + x(d) + "," + (h / 2) + ")"; })
66+
.attr("d", d3.svg.symbol().type(String).size(200));
67+
68+
svg.append("svg:g")
69+
.attr("class", "brush")
70+
.call(d3.svg.brush().x(x)
71+
.on("brushstart", brushstart)
72+
.on("brush", brush)
73+
.on("brushend", brushend))
74+
.selectAll("rect")
75+
.attr("height", h);
76+
77+
function brushstart() {
78+
svg.classed("selecting", true);
79+
}
80+
81+
function brush() {
82+
var s = d3.event.target.extent();
83+
symbol.classed("selected", function(d) { return s[0] <= (d = x(d)) && d <= s[1]; });
84+
}
85+
86+
function brushend() {
87+
svg.classed("selecting", !d3.event.target.empty());
88+
}
89+
90+
</script>
91+
</body>
92+
</html>

src/scale/scale.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,7 @@ function d3_scaleExtent(domain) {
44
var start = domain[0], stop = domain[domain.length - 1];
55
return start < stop ? [start, stop] : [stop, start];
66
}
7+
8+
function d3_scaleRange(scale) {
9+
return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range());
10+
}

src/svg/axis.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ d3.svg.axis = function() {
4646
tickTransform;
4747

4848
// Domain.
49-
var range = scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range()),
49+
var range = d3_scaleRange(scale),
5050
path = g.selectAll(".domain").data([0]),
5151
pathEnter = path.enter().append("svg:path").attr("class", "domain"),
5252
pathUpdate = transition(path);

src/svg/brush.js

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,12 @@ d3.svg.brush = function() {
4444
// Initialize the background to fill the defined range.
4545
// If the range isn't defined, you can post-process.
4646
if (x) {
47-
e = d3_scaleExtent(x.range());
47+
e = d3_scaleRange(x);
4848
bg.attr("x", e[0]).attr("width", e[1] - e[0]);
4949
d3_svg_brushRedrawX(g, extent);
5050
}
5151
if (y) {
52-
e = d3_scaleExtent(y.range());
52+
e = d3_scaleRange(y);
5353
bg.attr("y", e[0]).attr("height", e[1] - e[0]);
5454
d3_svg_brushRedrawY(g, extent);
5555
}
@@ -126,11 +126,13 @@ d3.svg.brush = function() {
126126
// Invert the pixel extent to data-space.
127127
if (!arguments.length) {
128128
if (x) {
129-
x0 = x.invert(extent[0][0]), x1 = x.invert(extent[1][0]);
129+
x0 = extent[0][0], x1 = extent[1][0];
130+
if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1);
130131
if (x1 < x0) t = x0, x0 = x1, x1 = t;
131132
}
132133
if (y) {
133-
y0 = y.invert(extent[0][1]), y1 = y.invert(extent[1][1]);
134+
y0 = extent[0][1], y1 = extent[1][1];
135+
if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1);
134136
if (y1 < y0) t = y0, y0 = y1, y1 = t;
135137
}
136138
return x && y ? [[x0, y0], [x1, y1]] : x ? [x0, x1] : y && [y0, y1];
@@ -140,14 +142,14 @@ d3.svg.brush = function() {
140142
if (x) {
141143
x0 = z[0], x1 = z[1];
142144
if (y) x0 = x0[0], x1 = x1[0];
143-
x0 = x(x0), x1 = x(x1);
145+
if (x.invert) x0 = x(x0), x1 = x(x1);
144146
if (x1 < x0) t = x0, x0 = x1, x1 = t;
145147
extent[0][0] = x0, extent[1][0] = x1;
146148
}
147149
if (y) {
148150
y0 = z[0], y1 = z[1];
149151
if (x) y0 = y0[1], y1 = y1[1];
150-
y0 = y(y0), y1 = y(y1);
152+
if (y.invert) y0 = y(y0), y1 = y(y1);
151153
if (y1 < y0) t = y0, y0 = y1, y1 = t;
152154
extent[0][1] = y0, extent[1][1] = y1;
153155
}
@@ -262,28 +264,30 @@ function d3_svg_brushMove() {
262264
}
263265

264266
function d3_svg_brushMove1(mouse, scale, i) {
265-
var range = d3_scaleExtent(scale.range()),
267+
var range = d3_scaleRange(scale),
268+
r0 = range[0],
269+
r1 = range[1],
266270
offset = d3_svg_brushOffset[i],
267271
size = d3_svg_brushExtent[1][i] - d3_svg_brushExtent[0][i],
268272
min,
269273
max;
270274

271275
// When dragging, reduce the range by the extent size and offset.
272276
if (d3_svg_brushDrag) {
273-
range[0] -= offset;
274-
range[1] -= size + offset;
277+
r0 -= offset;
278+
r1 -= size + offset;
275279
}
276280

277281
// Clamp the mouse so that the extent fits within the range extent.
278-
min = Math.max(range[0], Math.min(range[1], mouse[i]));
282+
min = Math.max(r0, Math.min(r1, mouse[i]));
279283

280284
// Compute the new extent bounds.
281285
if (d3_svg_brushDrag) {
282286
max = (min += offset) + size;
283287
} else {
284288

285289
// If the ALT key is pressed, then preserve the center of the extent.
286-
if (d3_svg_brushCenter) offset = Math.max(range[0], Math.min(range[1], 2 * d3_svg_brushCenter[i] - min));
290+
if (d3_svg_brushCenter) offset = Math.max(r0, Math.min(r1, 2 * d3_svg_brushCenter[i] - min));
287291

288292
// Compute the min and max of the offset and mouse.
289293
if (offset < min) {

0 commit comments

Comments
 (0)