Skip to content

Instantly share code, notes, and snippets.

@yelper
Last active May 20, 2016 16:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yelper/d38ddf461a0175ebd927946d15140947 to your computer and use it in GitHub Desktop.
Save yelper/d38ddf461a0175ebd927946d15140947 to your computer and use it in GitHub Desktop.
Getting hover-interaction and brushing to co-exist
license: cc-by-sa-4.0

Trying to get point hover-interaction and brushing to exist. I have found some existing precedence (bug #1604) for this being a weird problem, particular in relation to how D3's brush is implemented. Previous solutions have suggested drawing points on top and passing the event along to the brush, but this does not handle the case where the brush extent needs to be persistent (in the bigger picture, the scatterplot is linked to other components).

As implemented above, the brush draws fine and information can be recovered from points on hover. However, if the brush is started, dragged, or resized with the mouse starting on a point, strange behavior occurs (e.g. trying to translate an extent by clicking on a point will cause the extent to resize instead).

Here's what I've got: in brushstart() defined in D3.js (line 9192), there are two targets of interest: this (which is the element that brush is called on), and eventTarget (the actual element that was clicked). For the different operations (dragging, resizing, and "redrawing"), this should be the parent brush element (in this case, the svg element), and eventTarget should be the child element that was actually clicked (rect.extent for translating the extent, rect with datum for resizing, and rect.background for redrawing/canceling).

I can't seem to set the eventTarget property within brushstart() appropriately. Any ideas?

<!DOCTYPE html>
<meta charset="utf-8">
<style>
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.hidden {
opacity: 0.3;
}
.extent {
fill: #000;
fill-opacity: .125;
stroke: #fff;
}
</style>
<body>
<script src="//d3js.org/d3.v3.js"></script>
<script>
var margin = {top: 20, right: 50, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 450 - margin.top - margin.bottom;
var x = d3.scale.linear()
.range([0, width])
.domain([0, 10]);
var y = d3.scale.linear()
.range([height, 0])
.domain([0, 10]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var curPt = d3.select('body')
.append('p')
.html("Current point: ")
.append('span')
.attr('id', 'curPt');
var svg = d3.select('body').insert('svg', 'p')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,'+height+')')
.call(xAxis);
svg.append('g')
.attr('class', 'y axis')
.call(yAxis);
var brush = d3.svg.brush()
.x(x)
.y(y)
.on("brush", function() {
var e = brush.extent();
svg.selectAll('circle').classed('hidden', function(d) {
return e[0][0] > d[0] || d[0] > e[1][0]
|| e[0][1] > d[1] || d[1] > e[1][1];
}
);
})
.on("brushend", function() {
if (brush.empty()) svg.selectAll('circle').classed('hidden', false);
});
svg.call(brush);
var data = d3.range(50).map(function() { return [Math.random() * 10, Math.random() * 10]; });
svg.append('g')
.attr('class', 'points')
.selectAll('circle')
.data(data).enter()
.append('circle')
.attr('cx', function(d) { return x(d[0]); })
.attr('cy', function(d) { return y(d[1]); })
.attr('r', 10)
.style('fill', 'steelblue')
.on('mouseover', function(d) {
curPt.html("[" + d[0] + ", " + d[1] + "]");
})
.on('mouseout', function(d) {
curPt.html("");
})
.on('mousedown', function() {
var bubbleEvent = new Event('mousedown');
bubbleEvent.pageX = d3.event.pageX;
bubbleEvent.clientX = d3.event.clientX;
bubbleEvent.pageY = d3.event.pageY;
bubbleEvent.clientY = d3.event.clientY;
// figure out where we are
var pos = d3.mouse(this);
var b = d3.select('rect.extent').node().getBBox();
// test if outside of bounds of brush extent
if (pos[0] < b.x - 3 || pos[1] < b.y - 3 ||
pos[0] > b.x + b.width + 3 ||
pos[1] > b.y + b.height + 3) {
// also do special things for resizing rects, but not necessary in this example
// bubbleEvent.target = d3.select('rect.background').node();
d3.select('rect.background').node().dispatchEvent(bubbleEvent);
}
else {
// bubbleEvent.target = d3.select('rect.extent').node();
d3.select('rect.extent').node().dispatchEvent(bubbleEvent);
}
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment