statgen / locuszoom

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

Conditional Analysis UI #74

Closed Frencil closed 8 years ago

Frencil commented 8 years ago

Overview

This branch introduces UI and API hooks for more graceful control over model building with an initial emphasis on a list of covariates. Here are the underlying assumptions of this approach:

To begin, we need a UI element that serves as both an indicator to aggregate changes in our model as well as provide access to a menu with detail and controls. That is achieved in this approach by a new addition to the panels control element (the element that has buttons to move panels around vertically, remove panels, or access the panel description).

In the controls parameter for the panel layout we can now define whether a panel should show the conditional indicator:

panel_layout = {
  ...
  controls: {
    description: true,
    reposition: true,
    remove: true,
    model: true
  },
  ...
}

The model parameter within controls defaults to false for all panels, and so must be explicitly enabled. This also allows for explicit selection of which panels should show information about the model (e.g. the genes panel should never show it).

The model element that appears in the controls area behaves as both an indicator of the model represented in the state (presently by how many covariates it has) and as a UI element to access and edit, for now at least, the covariates in the state. Clicking the model element bring up an HTML overlay similar to the description overlay (formalized in this branch as "menus"). This overlay is automatically populated with a table of elements that are a part of the model covariates array with the ability to remove them individually or remove all of them.

State and Model Covariates API Methods

This branch only goes so far as defining a standard method for storing an arbitrarily complex model object in the state that, at a minimum, includes an array for covariates. It is still up to the implementer extending custom data sources to use what's stored in the state appropriately for how they plan to execute a requests that implement, for example, conditional analysis using model covariates.

The standard approach defined in this branch is to use the model object and covariates array:

var layout = {
  state: {
    model: {
      covariates: []
    }
  }
}

Since ideally a variety of different things can be covariates the working assumption is that entries in the covariates array will be whatever is appropriate.

There are new instance/plot level methods for working with state.model.covariates:

Each of the above methods includes a built-in trigger to applyState(). If there is a data source that is trained to alter its request based on the contents of state.model this should result in an immediate request for new data.

Additional Changes

DataLayer.applyDataMethods

Since elements in the conditions array can be anything it can be tricky to display them in a general way. The approach to that outlined in this branch is to define a new abstract method on the data layer prototype called applyDataMethods.

By default applyDataMethods applies three methods to each element in a data set:

This method also invokes applyCustomDataMethods(), a stub intended to be reimplemented as needed by data layers of particular types that may need other methods present on all elements in the data set (or to redefine the defaults - e.g. define a more specific toHTML() method).

LocusZoom.getToolTipData

To make it easy to drop a button in a tool tip that allows for adding that tool tip's parent element to the conditional analysis it was doable but required some ugly code. LocusZoom.getToolTipData was created as a shortcut for going from the element context of any node in any tool tip to the actual data that tool tip represents.

First off, the createToolTip method uses d3's data joins to actually join the data for the tool tip directly to the outermost HTML node representing the tool tip. Then this function can be called by any element within the tool tip using the this context (e.g. in the onclick handler for a button: console.log(LocusZoom.getToolTipData(this)) should log the data to the console).

From there this can be wrapped in a call to conditionOn for the plot to make a button in a tooltip add the data to state.condition and fire the request for new data as seen in the standard layout:

<button onclick="plot.conditionOn(LocusZoom.getToolTipData(this));">Condition</button>

Presumably the ability to get the data behind a tool tip from an arbitrary element inside that tool tip would be useful for other applications in the future. The method works by recursively jumping to parent nodes until it finds one classed as a LocusZoom tool tip, so should work for arbitrarily complex tool tip contents.

Panel Control Buttons class

This branch implements a standardized class for panel control buttons, as we now have definitions for five buttons (panel move up, panel move down, remove panel, show panel description, and show conditional analysis menu). Many buttons have patterns that could be abstracted, in particular the creation of a "menu" object that can be populated arbitrarily. The panel description button uses the menu object and just fills it with the description HTML while the model button has a more structured method for dynamically building the menu's contents based on what's in state.model.

This pattern was hit on when considering issue #75 - a menu for showing current data layers in the panel with UI to reorder them, toggle them on/off, or remove them. Such a menu could look very similar to the conditions menu and populate dynamically from the layout.

TODO

Frencil commented 8 years ago

@MrFlick - I've completed another pass over this branch that abstracts the model storage in state from being an array of conditions to a model object with a covariates array. Please take a look and let me know if it looks like I'm on the right track.

MrFlick commented 8 years ago

It's definitely the right track. But having function like LocusZoom.Instance.prototype.addModelCovariate for every possible Model property doesn't make sense. Just expose the model object and the model object might have addCovariate or addConditiningSNP or setRegions or setPhenotype or whatever. And I still think we should move the rendering of the model outside of the panel instance to a different class that we can bring in or swap out for different applications. Or do we tell people to write new panel types do be able to change the rendering? Is that how we make that extensible?

Frencil commented 8 years ago

Refactored branch to use more compartmentalized "Dashboard" model; all logic specific to covariates model building menu constrained to a single dashboard component definition. Pattern established for adding arbitrarily complex UI elements in a modular, isolated way. Squashing and merging.