Mousedown, mousemove, and mouseup events with multiple visualizations.

7,268 views
Skip to first unread message

gg

unread,
Jul 19, 2011, 5:06:09 PM7/19/11
to d3-js, gia...@uta.edu
Hello everyone.

I'm facing some problems with the series of mousedown, mousemove, and
mouseup events on multiple visualizations in a Web page.

I'm developing a Web page using D3 where different pairs of dimensions
of the data can be visualized on several scatter charts and would like
to implement a selection using a rectangle just like in the flower
scatter chart example.

Unlike the flower scatter chart example I do not know a priori how
many scatter charts are there, nor where they are, given that these
can be added by the user visualizing the Web page using buttons and
selecting the pairs of dimensions to visualize on each scatter chart.

Therefore, I would like to associate the mousemove and mouseup events
with the individual charts (just like the mousedown event is), instead
of the window as in the flower scatter chart example.

When I assign the events to the individual charts (and pass the
respective visualization to the rectangle so that the visualization
for which the event is fired is known), everything seems to work fine
with mousedown and mousemove events, but mouseup events are never
fired. This happens no matter whether the mouse pointer is within the
area of one of the scatter charts or not, be it the one where the
mousedown and mousemove fired or not.

The only situation when the mouseup event is fired is when the mouse
is not moved after the mousedown event, i.e., when no mousemove event
is fired between mousedown and mouseup.

As a result of not getting the mouseup event, I can never close the
selection using the rectangle.

What am I doing wrong?

Are there any requirements for using mouseup, mousemove, and mouseup
events that I'm neglecting?

What is a solution to my problem?

Thank you,

Gia

Mike Bostock

unread,
Jul 21, 2011, 9:18:29 PM7/21/11
to d3...@googlegroups.com, gia...@uta.edu
Hi Gia,

Typically you still want to associate the mousemove and mouseup events
with the window, even if you have multiple charts; this allows these
events to continue to work if the mouse goes outside of the chart that
started the event. For example, I might start brushing a selection and
then go slightly outside the chart (or window) boundary; it's nice if
the selection extent continues to update as I move the mouse, being
bounded by the chart size.

The problem, though, is that you can no longer assume that each event
listener is uniquely bound to a single chart. That can only be true of
the mousedown event. So the solution is to store some global state
that records where the mousedown originated. For example, imagine that
`svg` is a selection containing multiple SVG elements. I can record
which element started the mousedown in a global variable:

var dragging;

svg.on("mousedown", function() { var dragging = this; });

Then, I can register mousemove and mouseup listeners on the window,
that check against this global:

d3.select(window).on("mousemove", function() { if (!dragging)
return; console.log("dragging: " + dragging); });

In this case, `dragging` stores the SVG element. You can then
d3.select(dragging) to create a selection from the element. If you
prefer, you could store the data (d) associated with the SVG element
in a global variable instead.

But, getting back to the missing mouseup events, it's difficult to say
why that might occur without seeing the code. If the mouseup event
handler is registered on the SVG element, then you wouldn't receive
mouseup events if the mouseup occurs while the mouse is outside the
bounds of the SVG element (that's the motivation for listening to the
window—this guarantees you always receive the mouseup event).

Another possibility is something else is consuming the event. For
example, the force layout's drag behavior prevents click events from
firing if you drag nodes (so that you can drag nodes without
triggering clicks). Also, in the unlikely event that you register
multiple mouseup listeners, only the last listener is used (unless you
use a namespace, such as "mouseup.foo").

Mike

gg

unread,
Jul 22, 2011, 10:30:00 AM7/22/11
to d3-js, gia...@uta.edu
Thank you, Mike.

I understand the rationale behind registering the listeners on the
window. I was trying to understand what is the most modular solution I
could get. I'll introduce some global state and see how it goes.

As for the mouseup events, I understand they aren't dispatched to the
event handler registered on the specific chart, but the problem is
that they aren't dispatched even when I just mouse down on the
specific chart (and the mousedown event is fired and dispatched), move
the mouse a bit always within the specific chart boundaries (and the
mousemove events are fired and dispatched), and then mouse up on the
same specific chart. However, they're fired and consumed by my mouseup
event handler, when there is no mousemove event between the mousedown
and mouseup events.

Thus, I tend towards your second explanation and assume it might be
that whatever operation I perform when I serve the mousemove events
disconnects the mouseup event handler, or sets up something else to
consume them. I'm going to investigate this, and then get back with an
explanation and solution, or publish the sample Web page with the D3
example for you to review.

In the meantime, thanks again for your reply and congratulations to
you and all the team on D3 -- you really addressed many of the issues
I was facing when using Protovis (e.g., re-rendering speed).

Best,

Gia

gg

unread,
Jul 27, 2011, 5:16:16 PM7/27/11
to d3-js
Dear Mike,

I'm glad to let you know that I solved my problem regarding the
missing mouseup event.

As I mentioned in my first post, I'm registering the listeners for
events mousedown, mousemove, and mouseup on the svg:rect element in
each scatter chart, and not the window as in the Scatterplot Matrix
example.

For everyone's convenience, I'm pasting the code for the three events:

.on("mousedown", function(d) {

console.log("mousedown: " + d._uuid);

// Store current mouse position with respect to the svg:rect
element
d.x0 = d3.svg.mouse(this);

// Add a rectangle to the svg:g element
d.selectRect = d3.select(this.parentNode)
.append("svg:rect")
.style("fill", "#999")
.style("fill-opacity", .5);

// ???
d3.event.preventDefault();
})
.on("mousemove", function(d) {

console.log("mousemove: " + d._uuid);

if (!d.getSelectRect()) {
return;
}

console.log("mousemove: the selection rectangle exists");

// Store the current mouse position with respect to the svg:rect
element
d.x1 = d3.svg.mouse(this);

// Compute the corner coordinates
var minx = Math.min(d.x0[0], d.x1[0]),
maxx = Math.max(d.x0[0], d.x1[0]),
miny = Math.min(d.x0[1], d.x1[1]),
maxy = Math.max(d.x0[1], d.x1[1]);

// Resize the selection rectangle
d.selectRect.attr("x", minx + 5)
.attr("y", miny + 5)
.attr("width", maxx - minx - 5)
.attr("height", maxy - miny - 5);

d3.event.preventDefault();
})
.on("mouseup", function(d) {

console.log("mouseup: " + d._uuid);

if (!d.getSelectRect()) {
return;
}

console.log("mouseup: the selection rectangle exists");

// Remove the svg:rect element
d.getSelectRect().remove();

// Set the selection rectangle field to null
d.selectRect = null;
});

The problem was due to the grey selection rectangle used to highlight
the selected area. In fact, the mouseup event was not dispatched to
the underlying rectangle, because the pointer was on top of the
selection rectangle when the mouseup event was fired.

To solve this problem, I had to further reduce the size of the
selection rectangle (i.e., + 5 and - 5 in the x, y, width, and height
attributes).

It looks like the .5 and 1 used in the Scatterplot Matrix example were
not sufficient. (Or, the mouseup event is dispatched to the window no
matter what.) Perhaps you can pitch in here...

Thanks again for your help,

Gia

r34ch

unread,
Jul 29, 2011, 1:24:32 PM7/29/11
to d3...@googlegroups.com
I am confused as to why this does not work, can anyone be so kind to shed some light on this please?
 
.on("mousedown", function() { var dragging = 1 })
.on("mouseup", function() { var dragging = 0 })
.on("mousemove", function() { var m = d3.svg.mouse(this); if (dragging == 1) d3.select(this).select(".className").attr("y1", m[1]) });

Mike Bostock

unread,
Jul 29, 2011, 3:31:33 PM7/29/11
to d3...@googlegroups.com
The `var` is function-scoped, so it's not visible outside of your
event handlers. You need to declare `var dragging` outside of the
function, and then just say `dragging = 1;`.

Mike

r34ch

unread,
Jul 31, 2011, 7:53:59 AM7/31/11
to d3...@googlegroups.com
Doh. Thanks yet again.
Reply all
Reply to author
Forward
0 new messages