Understanding Events and Dispatch

6,178 views
Skip to first unread message

Guerino1

unread,
Mar 26, 2012, 6:06:42 PM3/26/12
to d3...@googlegroups.com
Hi,

I'm trying to understand event generation and utilization so I can apply it to my chart example, located at: http://bl.ocks.org/2164562.  In short, I want to enhance the chart code to highlight the bars, when I mouseover the legends and eventually do the reverse when I mouseover the bars, themselves.

Before doing so, I read Mike's "Internals" documentation, multiple times (yes, I'm dense and slow), as well as other examples to try and understand how to use Events and Dispatching.  I wanted to run my understanding of events and dispatching by the group, to check my logic.  Hopefully, writing it all out will help others learn from it, too.

My interpretation is that there are four (4) main steps to achieve in order to make it work...


1) First, I have to declare my event object, using d3.dispatch( types...).  For example...

var dispatch = d3.dispatch( "legendMouseover" )  // This creates the object that will be dispatched on mousing over the legends.


2) I then register a listener that will listen for the dispatched event object, on the vertical bars, themselves...

// Draw individual hyper text enabled bars...
canvas.selectAll("rect")
.data(dataSet)
.enter().append("svg:a") // Append legend elements        .attr("xlink:href", function(d) { return d.link; })
.append("svg:rect")
.attr("x", function(d, i) { return x(i); })
.attr("y", function(d) { return canvasHeight - y(d.magnitude) + 15; })
.attr("height", function(d) { return y(d.magnitude); }) 
.attr("width", barWidth)
.style("fill", function(d, i) { colorVal = colorScale(i); return colorVal; } )
.attr("color_value", function(d, i) { return colorScale(i); }) // Bar fill color...
.style("stroke", "white") // Bar border color...
.dispatch.on('legendMouseover', barMouseoverFunction);  // This will call the bar color changing function "barMouseOverFunction" when it receives the dispatched Event Object.


3) Then, I create a listener function that will be executed when the listener receives the dispatched event object...

var barMouseOverFunction = function() {
var bar = d3.select(this);
bar.style("fill", "Maroon" );
};


4) Finally, I ensure that, on mouseover (over the legend text), I dispatch the event object to the registered listeners by registering the event dispatcher with the Legend Text that will trigger it...

.on('mouseover', dispatch.legendMouseover()); // This 


So, in summary, the native "mouseover" event on the legend text will cause the text's dispatcher to transmit/dispatch an event object called "legendMouseover" to the listener that has been registered with the bars.  The listener that's been registered with the bars will then receive/catch the event.  Upon reception of the event, the bars' listener will, in turn, call the function "barMouseOverFunction" that changes the bars' fill color.

Does this seem like the correct implementation and execution logic?

Thanks for any help you can offer,

Frank


Mike Bostock

unread,
Mar 26, 2012, 11:32:53 PM3/26/12
to d3...@googlegroups.com
Why use d3.dispatch? Can you get by with just the native "mouseover"
and "mouseout" events?

I would only use d3.dispatch if you want to abstract some native event
sequence into higher-level events, such as how the d3.svg.brush
component translates a mousedown, mousemove and mouseup event sequence
(or likewise touchstart, touchmove, touchend) into brushstart, brush
and brushend.

Getting to your code:

> .on('mouseover', dispatch.legendMouseover());

Read this line closely. What you are doing first is calling
dispatch.legendMouseover. This will invoke all the "legendMouseover"
listeners, as described here:

https://github.com/mbostock/d3/wiki/Internals#wiki-dispatch

Then, you are taking the return value of this function call and
assigning that as the mouseover listener. The return value is, in
fact, undocumented! (I'll fix that.) However, if you look at the
implementation [1], you can see it is the dispatch object. This is an
object, not a function, so you'll get an Error in the console whenever
you mouseover.

If you intended to notify the "legendMouseover" listeners on
mouseover, then you must remove the parens, so that you're passing the
function to selection.on, rather than calling it:

.on("mouseover", dispatch.legendMouseover)

However, I don't see a strong reason to use a custom event here, when
your listeners could listen to the mouseover events directly.

Mike

[1] https://github.com/mbostock/d3/blob/master/src/core/dispatch.js#L30-37

Darron

unread,
Apr 4, 2012, 7:54:16 AM4/4/12
to d3...@googlegroups.com
Hi Mike (et al.),

I'm also a bit confused about custom events. I've set up a jsfiddle where I'm trying to use a custom drop event. Could someone please have a look and let me know what I'm doing wrong.



Regards,
Darron

Frank Guerino

unread,
Apr 5, 2012, 4:45:44 PM4/5/12
to d3...@googlegroups.com
Hi Mike,

I intentionally avoided this thread/topic as I focused on learning many other features while creating three examples that share some common features.  The examples are:
  1. "Multiple D3 Pie Charts Mixed In With Common HTML Layout Constructs"
  2. "Multiple D3 Based Horizontal Bar Charts Mixed In With Common HTML Layout Constructs"
  3. "Multiple D3 Vertical Bar Charts Mixed In With Common HTML Layout Constructs"
I've done things like...
  • Defining and using data structures that have multiple data elements for access and application
  • Drawing and placing multiple objects (Arcs, Rectangles, Circles, etc.) on different areas of a canvas
  • Creating and applying multiple SVG canvases to different areas of an HTML page that would have to segment such visualizations
  • Applying and placing text over objects, next to objects, etc.
  • Creating and applying simple event driven functions for mouseover and mouseout, for all objects
  • Applying simple transitions to Arcs and Bars
I think I'm now at the point where I can't avoid it, anymore, and I'm forced to figure out how this whole "event" thing works...

In each of the three examples above, I've essentially broken the charts into three sections...
  1. Color-coded chart objects (i.e. arcs or rectangles),
  2. Color-coded legend bullets (i.e circles), and
  3. Legend Text.
In all cases, I have individual mouseover and mouseout events that drive color changing callback functions, such that if I mouseover or mouseout of any single element, the fill color changes to maroon or back to its original color, respectively, only for that individual element.

I now need to figure out how to best create and apply a synchronization event that triggers all mouseover and mouseout callback functions, simultaneously, as I mouseover or mouseout of any single element.  So, for example, I'd like to be able to

I. Mouseover an Arc/Bar and...
1) have that Arc change fill color, and
2) have its corresponding bullet change fill color, and
3) have it's corresponding Legend Text change fill color
II. Mouseover a Legend Bullet and...
1) have that Legend Bullet change fill color, and
2) have its corresponding Arc/Bar change fill color, and
3) have it's corresponding Legend Text change fill color
III. Mouseover Legend Text and...
1) have that Legend Text change fill color, and
2) have its corresponding Legend Bullet change fill color, and
3) have it's corresponding Arc/Bar change fill color

In other words, I'd like to learn how to mouseover any single element in a synchronization set (i.e the Arc/Bar, the Legend Bullet, and the Legend Text) and ensure that all three correlating elements change color at the same time.

I know the only way to do this is to learn and understand events.  To you (Mike) and to the community, please bear with me as I ask lots of dumb questions.  I'll try not to be a PITA, as I do so...

Thanks for all your help,

Frank

Mike Bostock

unread,
Apr 6, 2012, 1:01:05 AM4/6/12
to d3...@googlegroups.com
> 1) have that Arc change fill color, and
> 2) have its corresponding bullet change fill color, and
> 3) have it's corresponding Legend Text change fill color

Break the larger problem into smaller problems. First: how would you
do the above? Given some way of identifying a set of related elements
(arc, legend, etc.), how would you select them and change their
appearance. Second: how can you trigger this changed based on a user
event, such as mouseover?

One method would be to give each related element a class. For example,
perhaps you would use the class "type-apple" for all the elements that
pertain to apples, while the elements that pertain to oranges have the
class "type-orange". The "type-" prefix serves only to avoid a name
collision with other classes you might use for styling. You can now
write a method that takes a type (a string, either "apple" or
"orange") and selects the related elements:

function highlight(type) {
d3.selectAll(".type-" + type)…
}

Of course, you might want to do different things for your legend
elements versus your arcs or bars. So you can make multiple more
specific selections:

function highlight(type) {
d3.selectAll(".legend.type-" + type)…
d3.selectAll(".arc.type-" + type)…
d3.selectAll(".bar.type-" + type)…
}

And of course, when you create the elements on initialization, you
must remember to give them the appropriate dynamic class name. For
example:

g.append("path")
.attr("class", function(d) { return "arc type-" + d.type; })
.attr("d", …)

This requires, of course, that the `type` property be resolved as a
single class name, and that it's unique across the types. You can't
use a name with spaces in it, for example. Sometimes, the easiest
thing to do is to use the index (0, 1, 2, 3, etc.) as a unique
identifier rather than using a human-readable name.

Once you've implemented the desired highlight behavior, you can enable
it on mouseover by accessing the data. For example:

g.append("path")
.attr("class", function(d) { return "arc type-" + d.type; })
.attr("d", …)
.on("mouseover", function(d) { highlight(d.type); })

In fact, if you want to be clever, you can pass the data directly to
the highlight function. And then you can use the same mouseover
function for all of your elements:

function mouseover(d) {
d3.selectAll(".legend.type-" + d.type)…
d3.selectAll(".arc.type-" + d.type)…
d3.selectAll(".bar.type-" + d.type)…
}

g.append("path")
.attr("class", function(d) { return "arc type-" + d.type; })
.on("mouseover", mouseover)

g.append("rect")
.attr("class", function(d) { return "bar type-" + d.type; })
.on("mouseover", mouseover)

You'll probably also want to define a mouseout (or `unhighlight`)
function, or allow the mouseover function to take null. In this case,
you'll revert the appearance of any highlighted elements.

Mike

Frank Guerino

unread,
Apr 6, 2012, 5:16:41 PM4/6/12
to d3...@googlegroups.com
Hi Mike,

Thanks for the help.  I get what you're saying and was going down the route of finding the best way to uniquely tag each object but hadn't come up with a good way to do so.

I have a question...  When you refer to "d.type", are you implying that the "type" (i.e. apple, orange, etc.) has somehow been incorporated into the data set "d", making it accessible from the data like any other value by name?

If so, there's only two real ways I can think of doing that...  First, I can just ensure the data is enriched with such unique identifiers before I ever pass it into the chart drawing function.  Or, second, I can take the array of arrays that has been passed into the chart drawing function and enrich it "after" it has been passed to the drawing function.

In the latter case, I was thinking I could immediately take the array of data "d" and run it through a loop that appends/pushes a unique ID onto each data array record, for example ("record_" + i), where "i" is the array's row index.  This would mean that I could now create "associative" custom type "sets" by using something like: 

".attr("class", function(d, i) { return "arc.type-" + d.recordID; })

Such sets would look like:

Set #1: ("arc.type-record_1", "legendBullet.type-record_1", "legendText.type-record_1")
Set #2: ("arc.type-record_2", "legendBullet.type-record_2", "legendText.type-record_2")
Set #3: ("arc.type-record_3", "legendBullet.type-record_3", "legendText.type-record_3")
Etc.

Does this sound reasonable or am I overcomplicating things?

Thanks,

Frank

Frank Guerino

unread,
Apr 7, 2012, 11:32:06 AM4/7/12
to d3...@googlegroups.com
Hi,

Synchronization of changes across multiple objects on a page now works for all charting examples.

The examples are located at:
NOTE: The bl.ocks viewer only allows you to see the first drawn chart.  To see all charts on page, you need to run the code locally.

I don't know if anyone is interested, but while implementing the code I ran into an interesting problem that needed to be addressed, which ultimately had to do with uniqueness across each chart on the page...

For anyone that has been following my examples, what I'm doing that makes my examples a little different than others that have been published is that, in all my examples, I redraw the same type of chart multiple times, with different data sets, all on the same page, and in different areas of the page, while mixing the charts in with common HTML layout constructs.

The final challenge related to using "events" was that I wanted to achieve synchronized change of multiple chart related objects (i.e. 1... an arc, 2... a legend bullet, and 3... a legend text string), all at the same time, based on mouseover or mouseout events.  In short, MIke's advice helped me understand that every object needed to, both, be..
  1. uniquely tagged to represent what type of object it was within a "set", and
  2. uniquely tagged to show which "set" the object belonged to (this is what Mike was talking about in his advice on using "types", like apples and oranges).
To solve the tagging of each individual object type, I used the following:
  1. Type A: "arc" (or "bar")
  2. Type B: "legendBullet"
  3. Type C: "legendText"
Given the above, I now know how to tag each object within a set.

To solve for uniqueness across "sets", I used the data index "i" to generate an index string "index-1", "index-2", etc.

The combination of the above two solutions allows me to have uniqueness within sets and across sets...

Set #1: (arc-index-1, legendBullet-index-1, legendText-index-1)
Set #2: (arc-index-2, legendBullet-index-2, legendText-index-2)
Set #3: (arc-index-3, legendBullet-index-3, legendText-index-3)
Etc.

However, given that my goal was to draw multiple pie charts on a page, I quickly ran into the issue of requiring "uniqueness across pie charts".  The problem manifested itself when I moused over "arc slice #1" in "pie #1" and I would not only see its corresponding legendBullet and legendText get highlighted but would "ALSO" see the corresponding set in all other pie charts get highlighted.  In other words, sets across pie charts were all tied together and mousing over anything in one pie chart would not only highlight that set's objects for that pie chart but would also highlight all objects in corresponding set for "ALL" pie charts.  This means that I needed to also solve for the problem of ensuring uniqueness across pie charts.

For now, I've implemented chart uniqueness by passing in a "chartID" string as part of the function call that draws the charts.  This allows me to manually name each chart that is drawn.  This doesn't strike me as the most elegant solution, because it requires the user to name each chart.  However, I always hate doing things like generating random number IDs because there's always the remote chance that the random number generator will generate the same number/ID.  Anyhow, implementing "chart uniqueness" now causes each set to look as follows...

Set #1: (pie1-arc-index-1, pie1-legendBullet-index-1, pie1-legendText-index-1)
Set #2: (pie2-arc-index-2, pie2-legendBullet-index-2, pie2-legendText-index-2)
Set #3: (pie3-arc-index-3, pie3-legendBullet-index-3, pie3-legendText-index-3)
Etc.

Anyhow, it all works and I've published the examples as gists (see above).  I hope others find them useful.  At some point, I hope we can integrate them (or versions of them) into a common set of charting libraries that everyone can benefit from.

Thanks to everyone that helped.  I really appreciate your time and knowledge.  Now, I can start to move on to some different visualizations!

My Best,

Frank



Frank Guerino

unread,
Apr 7, 2012, 11:37:57 AM4/7/12
to d3...@googlegroups.com
Oops, I wanted to correct the representation across charts...  Implementing chart uniqueness now causes sets to look as follows...

For Chart #1...

Set #1: (chart1-arc-index-1, chart1-legendBullet-index-1, chart1-legendText-index-1)
Set #2: (chart1-arc-index-2, chart1-legendBullet-index-2, chart1-legendText-index-2)
Set #3: (chart1-arc-index-3, chart1-legendBullet-index-3, chart1-legendText-index-3)
Etc.

For Chart #2...

Set #1: (chart2-arc-index-1, chart2-legendBullet-index-1, chart2-legendText-index-1)
Set #2: (chart2-arc-index-2, chart2-legendBullet-index-2, chart2-legendText-index-2)
Set #3: (chart2-arc-index-3, chart2-legendBullet-index-3, chart2-legendText-index-3)
Etc.

For Chart #3...

Set #1: (chart3-arc-index-1, chart3-legendBullet-index-1, chart3-legendText-index-1)
Set #2: (chart3-arc-index-2, chart3-legendBullet-index-2, chart3-legendText-index-2)
Set #3: (chart3-arc-index-3, chart3-legendBullet-index-3, chart3-legendText-index-3)
Etc.

This should make more sense.

Frank


Message has been deleted

Matt Gardner

unread,
Jun 18, 2014, 3:47:08 PM6/18/14
to d3...@googlegroups.com
In terms of chart reusability, selecting by class name to coordinate behaviors like mouseover events can conflict with existing charts. Although this solution definitely works for one-off use-cases. 

kuan kuan

unread,
Jan 12, 2015, 12:01:14 PM1/12/15
to d3...@googlegroups.com, mbos...@cs.stanford.edu

Hi, Mike:


I got one specific question about D3 brush:

I am trying to use brush to brush select range of data on a axis, the select work well, but one thing not perfect is when you click  in the brush area which may trigger mousedown.brush event, it will clear previous brush area, I wonder how can I move this behavior into brush move action rather than brush start?


 
Reply all
Reply to author
Forward
0 new messages