statgen / locuszoom

A Javascript/d3 embeddable plugin for interactively visualizing statistical genetic data from customizable sources.
https://statgen.github.io/locuszoom/
MIT License
154 stars 29 forks source link

Event Hooks #70

Closed Frencil closed 8 years ago

Frencil commented 8 years ago

This small branch expands the "onUpdate" functionality on plots to named "event hooks" that live on both plots and panels.

The old way

Before, an implementer would register an onUpdate function like this:

plot.onUpdate(function(){
  ...
});

And all registered onUpdate functions would fire whenever the layout changed. The layout changes every time state changes, or the geometry of the plot changes. This was needed to make the plot builder show the layout contents change in real time, but is too noisy for implementers wanting to hook to less frequent events.

The new way

Now an implementer does this:

plot.on("layout_changed", function(){
  ...
});

And can register many functions to named events.

Supported events

Two named events are supported in this branch:

The on(event , hook) method will do nothing if passed an event that is not present in the object's internal event_hooks object. Adding support for new event types would consist of adding an array to track registered functions in the event_hooks object and adding triggers to that event where appropriate in the core library.

The plot builder demo has been updated to demonstrate hooks to both supported events at the instance level.

Plots and Panels

The same event hooks framework with the accompanying on(event, hook) function has been applied to instances/plots as well as to panels. The plot builder demonstrates panel-level event hooking by showing a "loading" overlay on the new panel when adding an analysis.

MrFlick commented 8 years ago

Does this not allow for parameters to be passed along with events? I was looking at something similar recently: https://github.com/Reactive-Extensions/RxJS/blob/master/doc/howdoi/eventemitter.md . It may make sense to use .on() to allow for event subscriptions, and .emit() to actually kick them off rather than overloading .on(). It would be nice to pass along at least a reference to the object firing the event.

Frencil commented 8 years ago

@MrFlick take a look now. I like the notion of on() vs. emit() so that's now in place. As hooks are added to event hook arrays their context is bound to the parent object like so:

this.event_hooks[event].push(hook.bind(this));

The result is that this in an event hook bound to the plot will be the plot, and this in an event hook bound to a panel will be the panel.

As for parameters to pass into event hooks, I'm not sure what use cases would be or how best to implement that. That may make more sense in a follow-up iteration if we can't think of any immediate use cases right now. Either way, can you describe how you might picture such a feature?

MrFlick commented 8 years ago

I guess I was thinking of events like point_clicked or something. We could pass along the point that was clicked as a parameter so make it easier to do something with it or sync up with other data on the page. That way you could do emit("point_clicked", {chr:1, pos:10000}).

Frencil commented 8 years ago

Okay, take a look now. If you load up plot builder and click on any data element (point or gene) you should see a note in the console coming from this registered event hook:

plot.on("element_clicked", function(){
  console.log("element clicked: ", this);
});

Note that the emit trigger had to be added in two places, because adding events with d3's on() method overwrites any currently in place on a selection. Thus the scatter and genes data layers create onclick event handlers that do nothing more than trigger emit events, passing along the element context. Then the recently refactored system for applying status behaviors from the layout defines these emitters again, as it may be overwriting the onclick method with a status behavior (e.g. toggle whether a point is selected).

Overall this seems to work pretty well, and it establishes a pattern for adding plenty of other supported events as use cases pop up.