|
(function(d3) { |
|
d3.svg.polybrush = function() { |
|
var dispatch = d3.dispatch("brushstart", "brush", "brushend"), |
|
x = null, |
|
y = null, |
|
extent = [], |
|
firstClick = true, |
|
firstTime = true, |
|
wasDragged = false, |
|
origin = null, |
|
line = d3.svg.line() |
|
.x(function(d) { |
|
return d[0]; |
|
}) |
|
.y(function(d) { |
|
return d[1]; |
|
}); |
|
var brush = function(g) { |
|
g.each(function() { |
|
var bg, e, fg; |
|
g = d3.select(this); |
|
bg = g.selectAll(".background").data([0]); |
|
fg = g.selectAll(".extent").data([extent]); |
|
g.style("pointer-events", "all").on("click.brush", addAnchor); |
|
bg.enter().append("rect").attr("class", "background").style("visibility", "hidden").style("cursor", "crosshair"); |
|
fg.enter().append("path").attr("class", "extent").style("cursor", "move"); |
|
if (x) { |
|
e = scaleExtent(x.range()); |
|
bg.attr("x", e[0]).attr("width", e[1] - e[0]); |
|
} |
|
if (y) { |
|
e = scaleExtent(y.range()); |
|
bg.attr("y", e[0]).attr("height", e[1] - e[0]); |
|
} |
|
}); |
|
}; |
|
var drawPath = function() { |
|
return d3.selectAll("g path").attr("d", function(d) { |
|
return line(d) + "Z"; |
|
}); |
|
}; |
|
var scaleExtent = function(domain) { |
|
var start, stop; |
|
start = domain[0]; |
|
stop = domain[domain.length - 1]; |
|
if (start < stop) { |
|
return [start, stop]; |
|
} else { |
|
return [stop, start]; |
|
} |
|
}; |
|
var withinBounds = function(point) { |
|
var rangeX, rangeY, _x, _y; |
|
rangeX = scaleExtent(x.range()); |
|
rangeY = scaleExtent(y.range()); |
|
_x = Math.max(rangeX[0], Math.min(rangeX[1], point[0])); |
|
_y = Math.max(rangeY[0], Math.min(rangeY[1], point[1])); |
|
return point[0] === _x && point[1] === _y; |
|
}; |
|
var moveAnchor = function(target) { |
|
var moved, point; |
|
point = d3.mouse(target); |
|
if (firstTime) { |
|
extent.push(point); |
|
firstTime = false; |
|
} else { |
|
if (withinBounds(point)) { |
|
extent.splice(extent.length - 1, 1, point); |
|
} |
|
drawPath(); |
|
dispatch.brush(); |
|
} |
|
}; |
|
var closePath = function() { |
|
var w; |
|
w = d3.select(window); |
|
w.on("dblclick.brush", null).on("mousemove.brush", null); |
|
firstClick = true; |
|
if (extent.length === 2 && extent[0][0] === extent[1][0] && extent[0][1] === extent[1][1]) { |
|
extent.splice(0, extent.length); |
|
} |
|
d3.select(".extent").on("mousedown.brush", moveExtent); |
|
return dispatch.brushend(); |
|
}; |
|
var addAnchor = function() { |
|
var g, w, |
|
_this = this; |
|
g = d3.select(this); |
|
w = d3.select(window); |
|
firstTime = true; |
|
if (wasDragged) { |
|
wasDragged = false; |
|
return; |
|
} |
|
if (firstClick) { |
|
extent.splice(0, extent.length); |
|
firstClick = false; |
|
d3.select(".extent").on("mousedown.brush", null); |
|
w.on("mousemove.brush", function() { |
|
return moveAnchor(_this); |
|
}).on("dblclick.brush", closePath); |
|
dispatch.brushstart(); |
|
} |
|
if (extent.length > 1) { |
|
extent.pop(); |
|
} |
|
extent.push(d3.mouse(this)); |
|
return drawPath(); |
|
}; |
|
var dragExtent = function(target) { |
|
var checkBounds, fail, p, point, scaleX, scaleY, updateExtentPoint, _i, _j, _len, _len1; |
|
point = d3.mouse(target); |
|
scaleX = point[0] - origin[0]; |
|
scaleY = point[1] - origin[1]; |
|
fail = false; |
|
origin = point; |
|
updateExtentPoint = function(p) { |
|
p[0] += scaleX; |
|
p[1] += scaleY; |
|
}; |
|
for (_i = 0, _len = extent.length; _i < _len; _i++) { |
|
p = extent[_i]; |
|
updateExtentPoint(p); |
|
} |
|
checkBounds = function(p) { |
|
if (!withinBounds(p)) { |
|
fail = true; |
|
} |
|
return fail; |
|
}; |
|
for (_j = 0, _len1 = extent.length; _j < _len1; _j++) { |
|
p = extent[_j]; |
|
checkBounds(p); |
|
} |
|
if (fail) { |
|
return; |
|
} |
|
drawPath(); |
|
return dispatch.brush({ |
|
mode: "move" |
|
}); |
|
}; |
|
var dragStop = function() { |
|
var w; |
|
w = d3.select(window); |
|
w.on("mousemove.brush", null).on("mouseup.brush", null); |
|
wasDragged = true; |
|
return dispatch.brushend(); |
|
}; |
|
var moveExtent = function() { |
|
var _this = this; |
|
d3.event.stopPropagation(); |
|
d3.event.preventDefault(); |
|
if (firstClick && !brush.empty()) { |
|
d3.select(window).on("mousemove.brush", function() { |
|
return dragExtent(_this); |
|
}).on("mouseup.brush", dragStop); |
|
origin = d3.mouse(this); |
|
} |
|
}; |
|
brush.isWithinExtent = function(x, y) { |
|
var i, j, len, p1, p2, ret, _i, _len; |
|
len = extent.length; |
|
j = len - 1; |
|
ret = false; |
|
for (i = _i = 0, _len = extent.length; _i < _len; i = ++_i) { |
|
p1 = extent[i]; |
|
p2 = extent[j]; |
|
if ((p1[1] > y) !== (p2[1] > y) && x < (p2[0] - p1[0]) * (y - p1[1]) / (p2[1] - p1[1]) + p1[0]) { |
|
ret = !ret; |
|
} |
|
j = i; |
|
} |
|
return ret; |
|
}; |
|
brush.x = function(z) { |
|
if (!arguments.length) { |
|
return x; |
|
} |
|
x = z; |
|
return brush; |
|
}; |
|
brush.y = function(z) { |
|
if (!arguments.length) { |
|
return y; |
|
} |
|
y = z; |
|
return brush; |
|
}; |
|
brush.extent = function(z) { |
|
if (!arguments.length) { |
|
return extent; |
|
} |
|
extent = z; |
|
return brush; |
|
}; |
|
brush.clear = function() { |
|
extent.splice(0, extent.length); |
|
return brush; |
|
}; |
|
brush.empty = function() { |
|
return extent.length === 0; |
|
}; |
|
|
|
d3.rebind(brush, dispatch, "on"); |
|
|
|
return brush; |
|
}; |
|
})(d3); |