You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: assets/docs/interactivity.md
+36-3Lines changed: 36 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -64,13 +64,46 @@ Each LocusZoom rendering is controlled by a declarative set of layout options. I
64
64
In practice, this is the key idea behind the `display_options` widget, a built-in feature that handles such mutations in a controlled fashion. If you are doing this using your own code, the following "gotchas" apply:
65
65
66
66
* When the layout is defined before first render, it uses abstract syntax (eg `{{namespace[assoc]field}}`. To modify an existing plot layout after it has been rendered, you will need to use concrete syntax in which the namespace has been filled in: `assoc2_mystudy:field`.
67
-
* LocusZoom layouts are nested and hierarchical (plot --> panels[] --> data_layers[]). Writing a layout thus involves awkward syntax such as `layout.panels[0].data_layers[1]`. This is very fragile if you frequently add or rename panels: when writing code that modifies a layout, ask what you can do to make it maintainable.
68
-
* Make it semantically clear which item is being modified, in case the array changes later: `const assoc_panel_layout = panel_layouts[0]`
69
-
* Instead of hard-coding position in an array, consider dynamically locating the desired section by a property that may change less often: `const assoc_layer = assoc_panel_layout.data_layers.find(item => item.id === 'association')`
67
+
* LocusZoom layouts are nested and hierarchical (plot --> panels[] --> data_layers[]). See the helper functions below for advice on how to write mutations that are more readable and maintainable.
70
68
* Be conservative in how many fields you allow to be changed. Layouts allow almost any aspect of the plot to be customized, but it can be difficult to test every possible combination. It's generally easier to code and maintain controlled options (like a list of preset views).
71
69
72
70
After re-defining the layout, be sure to call `plot.applyState()` (also known as `plot.refresh()`) to trigger a re-render, so that the changes to the layout take effect.
73
71
72
+
### Helper functions for modifying nested layouts
73
+
The "building block" style of layouts makes it easy to reuse pieces, but customizing part of a layout after rendering can be very clunky (example: `layout.panels[0].data_layers[1]`). In particular, if the order of elements in the layout ever changed (like adding a new panel or toolbar button), then code that accessed items by array position would break in ways that are very hard to debug. This is a maintainability headache.
74
+
75
+
As an alternative, a helper function `LocusZoom.Layouts.mutate_attrs` can be used to modify all parts of a layout that match a selector, using a readable syntax based on the [JsonPath](https://goessner.net/articles/JsonPath/) query language. See the developer documentation for further details.
76
+
77
+
Examples:
78
+
```javascript
79
+
// Add a field to a data layer, taking into account what fields are already there. The third argument is a function that receives the old value and returns the new one
// When the user clicks a button on the page, change what field is used for the y-axis for all association scatter plots. In this syntax, all matches receive the same value (the last argument is a value, instead of a callable function).
// The mutation function is not limited to changing scalar values or lists. If the selector targets a compound object, the function can be used to modify several properties all at once. Make sure to return the resulting config object when done.
// For debugging purposes, there is a read-only function that can be used to verify that a selector works as expected. It will return a list, one item per result.
We do not implement the entire JsonPath specification. The syntax used by LocusZoom:
96
+
97
+
- DOES support single child (`.`), deep nested (`..`), and wild-card (`*`) accessors
98
+
- DOES support filtering arrays-of-config-objects to only items that match a simple single-attribute-exact-match predicate (`$.panels_array[?(@.akeyhasvalue === "targetvalue")]`)
99
+
- DOES support queries that nest/combine operators (`$..data_layers[?(@.tag === 'association')].fields`)
100
+
- DOES NOT support complex JS expressions in predicates (which would be a security issue), or indexing array items. (writing layouts based on item[0] is a maintainability anti-pattern, and we are actively trying to discourage doing that)
101
+
- The end result of all selectors used should be to return a specific key inside an object. Lists can be filtered, but not indexed.
102
+
103
+
Most pre-made data layer and panel layouts now contain a `tag` field, which can be used to write semantically meaningful selectors, like, "modify all scatter plots that show GWAS association data".
104
+
105
+
> This helper function is aimed at making quick changes to one or two fields (before render), or more complex customizations (after render). If you are trying to make complex customizations to a layout when it is first defined, it is often better to build up in pieces so that you have more control of the result. For example, customizing a single data layer as part of a layout: `LocusZoom.Layouts.get('data_layer', 'association', { id: 'customoverridevalue' })`.
106
+
74
107
## Events communicate with the outside world
75
108
Each time that a LocusZoom plot is modified, it fires an *event* that notifies any listeners of the change.
0 commit comments