Skip to content

Instantly share code, notes, and snippets.

@yelper
Last active May 23, 2016 17:49
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/dc2bbff489dbf0820ccf8d73537f071c to your computer and use it in GitHub Desktop.
Save yelper/dc2bbff489dbf0820ccf8d73537f071c to your computer and use it in GitHub Desktop.
Getting brushing/hovering to co-exist (attempt 2: disable hover mouse-events)
license: cc-by-sa-4.0

EDIT from last gist: Trying to disable mouse-events on the points while a click is detected (the third solution suggested in #1604). It almost works, but takes two clicks to brush when clicking on a point (one to disable the mouse-events on the point, the second click finally gets sent to the brush).

old text follows

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);
d3.select('.points').style('pointer-events', null);
dispatched = false;
});
var dispatched = false;
svg.append('g').attr('class', 'brush').call(brush);
svg.on('mousedown', function() {
d3.select('.points').style('pointer-events', 'none');
if (!dispatched) {
dispatched = true;
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;
// d3.select('.brush').node().dispatchEvent(bubbleEvent);
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('.brush rect.background').node().dispatchEvent(bubbleEvent);
}
else {
d3.select('.brush rect.extent').node().dispatchEvent(bubbleEvent);
}
}
}, true);
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