Permalink
Browse files

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.
  • Loading branch information...
1 parent 552622d commit 532bf71766904a11b5b2ec4f4f2dcc18c49f3475 @mbostock mbostock committed Nov 23, 2011
Showing with 134 additions and 26 deletions.
  1. +20 −12 d3.js
  2. +2 −2 d3.min.js
  3. +92 −0 examples/brush/brush-ordinal.html
  4. +4 −0 src/scale/scale.js
  5. +1 −1 src/svg/axis.js
  6. +15 −11 src/svg/brush.js
View
32 d3.js
@@ -2325,6 +2325,10 @@ function d3_scaleExtent(domain) {
var start = domain[0], stop = domain[domain.length - 1];
return start < stop ? [start, stop] : [stop, start];
}
+
+function d3_scaleRange(scale) {
+ return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range());
+}
function d3_scale_nice(domain, nice) {
var i0 = 0,
i1 = domain.length - 1,
@@ -3814,7 +3818,7 @@ d3.svg.axis = function() {
tickTransform;
// Domain.
- var range = scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range()),
+ var range = d3_scaleRange(scale),
path = g.selectAll(".domain").data([0]),
pathEnter = path.enter().append("svg:path").attr("class", "domain"),
pathUpdate = transition(path);
@@ -4012,12 +4016,12 @@ d3.svg.brush = function() {
// Initialize the background to fill the defined range.
// If the range isn't defined, you can post-process.
if (x) {
- e = d3_scaleExtent(x.range());
+ e = d3_scaleRange(x);
bg.attr("x", e[0]).attr("width", e[1] - e[0]);
d3_svg_brushRedrawX(g, extent);
}
if (y) {
- e = d3_scaleExtent(y.range());
+ e = d3_scaleRange(y);
bg.attr("y", e[0]).attr("height", e[1] - e[0]);
d3_svg_brushRedrawY(g, extent);
}
@@ -4094,11 +4098,13 @@ d3.svg.brush = function() {
// Invert the pixel extent to data-space.
if (!arguments.length) {
if (x) {
- x0 = x.invert(extent[0][0]), x1 = x.invert(extent[1][0]);
+ x0 = extent[0][0], x1 = extent[1][0];
+ if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1);
if (x1 < x0) t = x0, x0 = x1, x1 = t;
}
if (y) {
- y0 = y.invert(extent[0][1]), y1 = y.invert(extent[1][1]);
+ y0 = extent[0][1], y1 = extent[1][1];
+ if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1);
if (y1 < y0) t = y0, y0 = y1, y1 = t;
}
return x && y ? [[x0, y0], [x1, y1]] : x ? [x0, x1] : y && [y0, y1];
@@ -4108,14 +4114,14 @@ d3.svg.brush = function() {
if (x) {
x0 = z[0], x1 = z[1];
if (y) x0 = x0[0], x1 = x1[0];
- x0 = x(x0), x1 = x(x1);
+ if (x.invert) x0 = x(x0), x1 = x(x1);
if (x1 < x0) t = x0, x0 = x1, x1 = t;
extent[0][0] = x0, extent[1][0] = x1;
}
if (y) {
y0 = z[0], y1 = z[1];
if (x) y0 = y0[1], y1 = y1[1];
- y0 = y(y0), y1 = y(y1);
+ if (y.invert) y0 = y(y0), y1 = y(y1);
if (y1 < y0) t = y0, y0 = y1, y1 = t;
extent[0][1] = y0, extent[1][1] = y1;
}
@@ -4230,28 +4236,30 @@ function d3_svg_brushMove() {
}
function d3_svg_brushMove1(mouse, scale, i) {
- var range = d3_scaleExtent(scale.range()),
+ var range = d3_scaleRange(scale),
+ r0 = range[0],
+ r1 = range[1],
offset = d3_svg_brushOffset[i],
size = d3_svg_brushExtent[1][i] - d3_svg_brushExtent[0][i],
min,
max;
// When dragging, reduce the range by the extent size and offset.
if (d3_svg_brushDrag) {
- range[0] -= offset;
- range[1] -= size + offset;
+ r0 -= offset;
+ r1 -= size + offset;
}
// Clamp the mouse so that the extent fits within the range extent.
- min = Math.max(range[0], Math.min(range[1], mouse[i]));
+ min = Math.max(r0, Math.min(r1, mouse[i]));
// Compute the new extent bounds.
if (d3_svg_brushDrag) {
max = (min += offset) + size;
} else {
// If the ALT key is pressed, then preserve the center of the extent.
- if (d3_svg_brushCenter) offset = Math.max(range[0], Math.min(range[1], 2 * d3_svg_brushCenter[i] - min));
+ if (d3_svg_brushCenter) offset = Math.max(r0, Math.min(r1, 2 * d3_svg_brushCenter[i] - min));
// Compute the min and max of the offset and mouse.
if (offset < min) {
View

Large diffs are not rendered by default.

Oops, something went wrong.
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
+ <title>Brush</title>
+ <script type="text/javascript" src="../../d3.js"></script>
+ <style type="text/css">
+
+svg {
+ font: 10px sans-serif;
+}
+
+path {
+ -webkit-transition: fill-opacity 250ms linear;
+}
+
+.selecting path {
+ fill-opacity: .2;
+}
+
+.selecting path.selected {
+ stroke: #f00;
+ stroke-width: 2px;
+}
+
+.axis path, .axis line {
+ fill: none;
+ stroke: #000;
+ shape-rendering: crispEdges;
+}
+
+.brush .extent {
+ stroke: #fff;
+ fill-opacity: .125;
+ shape-rendering: crispEdges;
+}
+
+ </style>
+ </head>
+ <body>
+ <script type="text/javascript">
+
+var data = d3.svg.symbolTypes;
+
+var m = [10, 10, 20, 10],
+ w = 960 - m[1] - m[3],
+ h = 100 - m[0] - m[2];
+
+var x = d3.scale.ordinal().domain(data).rangePoints([0, w], 1);
+
+var svg = d3.select("body").append("svg:svg")
+ .attr("width", w + m[1] + m[3])
+ .attr("height", h + m[0] + m[2])
+ .append("svg:g")
+ .attr("transform", "translate(" + m[3] + "," + m[0] + ")");
+
+svg.append("svg:g")
+ .attr("class", "x axis")
+ .attr("transform", "translate(0," + h + ")")
+ .call(d3.svg.axis().scale(x).orient("bottom"));
+
+var symbol = svg.append("svg:g").selectAll("path")
+ .data(data)
+ .enter().append("svg:path")
+ .attr("transform", function(d) { return "translate(" + x(d) + "," + (h / 2) + ")"; })
+ .attr("d", d3.svg.symbol().type(String).size(200));
+
+svg.append("svg:g")
+ .attr("class", "brush")
+ .call(d3.svg.brush().x(x)
+ .on("brushstart", brushstart)
+ .on("brush", brush)
+ .on("brushend", brushend))
+ .selectAll("rect")
+ .attr("height", h);
+
+function brushstart() {
+ svg.classed("selecting", true);
+}
+
+function brush() {
+ var s = d3.event.target.extent();
+ symbol.classed("selected", function(d) { return s[0] <= (d = x(d)) && d <= s[1]; });
+}
+
+function brushend() {
+ svg.classed("selecting", !d3.event.target.empty());
+}
+
+ </script>
+ </body>
+</html>
View
@@ -4,3 +4,7 @@ function d3_scaleExtent(domain) {
var start = domain[0], stop = domain[domain.length - 1];
return start < stop ? [start, stop] : [stop, start];
}
+
+function d3_scaleRange(scale) {
+ return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range());
+}
View
@@ -46,7 +46,7 @@ d3.svg.axis = function() {
tickTransform;
// Domain.
- var range = scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range()),
+ var range = d3_scaleRange(scale),
path = g.selectAll(".domain").data([0]),
pathEnter = path.enter().append("svg:path").attr("class", "domain"),
pathUpdate = transition(path);
View
@@ -44,12 +44,12 @@ d3.svg.brush = function() {
// Initialize the background to fill the defined range.
// If the range isn't defined, you can post-process.
if (x) {
- e = d3_scaleExtent(x.range());
+ e = d3_scaleRange(x);
bg.attr("x", e[0]).attr("width", e[1] - e[0]);
d3_svg_brushRedrawX(g, extent);
}
if (y) {
- e = d3_scaleExtent(y.range());
+ e = d3_scaleRange(y);
bg.attr("y", e[0]).attr("height", e[1] - e[0]);
d3_svg_brushRedrawY(g, extent);
}
@@ -126,11 +126,13 @@ d3.svg.brush = function() {
// Invert the pixel extent to data-space.
if (!arguments.length) {
if (x) {
- x0 = x.invert(extent[0][0]), x1 = x.invert(extent[1][0]);
+ x0 = extent[0][0], x1 = extent[1][0];
+ if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1);
if (x1 < x0) t = x0, x0 = x1, x1 = t;
}
if (y) {
- y0 = y.invert(extent[0][1]), y1 = y.invert(extent[1][1]);
+ y0 = extent[0][1], y1 = extent[1][1];
+ if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1);
if (y1 < y0) t = y0, y0 = y1, y1 = t;
}
return x && y ? [[x0, y0], [x1, y1]] : x ? [x0, x1] : y && [y0, y1];
@@ -140,14 +142,14 @@ d3.svg.brush = function() {
if (x) {
x0 = z[0], x1 = z[1];
if (y) x0 = x0[0], x1 = x1[0];
- x0 = x(x0), x1 = x(x1);
+ if (x.invert) x0 = x(x0), x1 = x(x1);
if (x1 < x0) t = x0, x0 = x1, x1 = t;
extent[0][0] = x0, extent[1][0] = x1;
}
if (y) {
y0 = z[0], y1 = z[1];
if (x) y0 = y0[1], y1 = y1[1];
- y0 = y(y0), y1 = y(y1);
+ if (y.invert) y0 = y(y0), y1 = y(y1);
if (y1 < y0) t = y0, y0 = y1, y1 = t;
extent[0][1] = y0, extent[1][1] = y1;
}
@@ -262,28 +264,30 @@ function d3_svg_brushMove() {
}
function d3_svg_brushMove1(mouse, scale, i) {
- var range = d3_scaleExtent(scale.range()),
+ var range = d3_scaleRange(scale),
+ r0 = range[0],
+ r1 = range[1],
offset = d3_svg_brushOffset[i],
size = d3_svg_brushExtent[1][i] - d3_svg_brushExtent[0][i],
min,
max;
// When dragging, reduce the range by the extent size and offset.
if (d3_svg_brushDrag) {
- range[0] -= offset;
- range[1] -= size + offset;
+ r0 -= offset;
+ r1 -= size + offset;
}
// Clamp the mouse so that the extent fits within the range extent.
- min = Math.max(range[0], Math.min(range[1], mouse[i]));
+ min = Math.max(r0, Math.min(r1, mouse[i]));
// Compute the new extent bounds.
if (d3_svg_brushDrag) {
max = (min += offset) + size;
} else {
// If the ALT key is pressed, then preserve the center of the extent.
- if (d3_svg_brushCenter) offset = Math.max(range[0], Math.min(range[1], 2 * d3_svg_brushCenter[i] - min));
+ if (d3_svg_brushCenter) offset = Math.max(r0, Math.min(r1, 2 * d3_svg_brushCenter[i] - min));
// Compute the min and max of the offset and mouse.
if (offset < min) {

0 comments on commit 532bf71

Please sign in to comment.