Skip to content
Rodrigo García-Herrera edited this page Feb 18, 2019 · 3 revisions

The key components of Lyra's architecture are Redux, which is used to define a data store to hold the entire Lyra application state; and Vega, which is rendered from that Lyra application state to provide the visual end product.

There exists an intermediate Lyra application model (js/model/index.js) which serves as a bridge between these two: the model file defines ID-keyed dictionaries for different types of primitive,

Generally speaking, the basic flow of control in the application should always follow one of these two paths:

  1. User interacts w/ React component within Lyra component dispatches an action to the Redux store the store reducer updates the store according to the action received the store listener updates the intermediate model and that model is used to re-render or update Vega as needed
  2. User interacts w/ Vega view React components receive the update component dispatches an action Redux store updates to account for that action Store listener determines no further change is needed

This glosses over some subtleties of implementation but thinking about the application in these terms clarifies the required logic.


The Intermediate Application Model is essential because the redux store contains the most basic raw state objects, but primitive objects instantiated from classes are used to encapsulate business logic. As an example, to make a Rect object:

  1. An Add Primitive action is dispatched with data representing a rect mark
  2. The Redux store reducer digests that action
  • The store creates signal records for the presentational values of the Rect mark
  • The store creates a record for the Rect mark that points at those new signals
  • The store flags itself as needing a Vega re-parse for the change to be updated.
  1. The store listener sees that a re-parse has been requested, and instantiates the new Rect mark instance, which is stored in a private, ID-keyed dictionary within the model file
  2. With mark synchronization complete, the store listener goes on to request a model re-parse: the model serializes itself into a Vega spec object, and uses that spec to create a new Vega view
  3. The new Vega view is rendered in the document, and the visualization now visibly includes the new Rect mark.

To update a visual property of that rect mark:

  1. The user selects the Rect mark
  2. The inspector component for that Rect mark loads
  3. The inspector component binds Vega event listeners to handle any changes to signals within Vega
  4. The user changes the color of the Rect in the inspector
  5. The inspector sets that color in Vega and requests a Vega update: Vega is now up to date
  6. The Vega update triggers one of those bound event listeners within the component
  7. The bound event listener dispatches an action to the store with the new (color) signal value
  8. The store is now up to date with Vega

To update a non-visual property of that rect mark, e.g. its name property:

  1. The user double-clicks the name of the mark in the group list sidebar
  2. The user enters a new name
  3. The user hits enter or deselects the editable area
  4. The IDEAL FLOW, which we should get to eventually:
  5. Component dispatches an action to the store with the new name
  6. UI elements which derive value from the store update
  7. Listener subscribed to the store detects the change and updates the Rect mark instance contained within the intermediate application model's primitives store if necessary
  8. The CURRENT FLOW:
  9. Component updates the Rect mark instance directly
  10. Component dispatches an action to the store with the new name
  11. UI elements which derive value from the store update

To bind data to a mark:

  1. The user drags a data field and drops it over a mark's channel
  2. The DataTable component calls a method on the Rect mark instance to bind the provided data to the property represented by that channel
  3. That method called by the DataTable (defined in the rules subdirectory) determines what scales, axes etc need to be created
  • Currently these axes, scales and such are instantiated directly within the rules, and it is up to the rules code to make sure the references get set appropriately
  • Long-term those should be created by dispatching actions to the store, and the reducers would shoulder some of the burden of tying the values together
  1. Actions are dispatched from the rules code to update the relevant mark properties to point at the new datasource, scales, etc
  2. The view is flagged as invalid by the reducer, and the Model is used to update the Rect instance and re-render the vega view as in the "Add Mark" example