Skip to content

Commit

Permalink
migrate LocusZoom.mergeLayouts to LocusZoom.Layouts.merge, update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
Frencil committed Oct 14, 2016
1 parent 13d03ea commit 5d4c33d
Show file tree
Hide file tree
Showing 18 changed files with 268 additions and 330 deletions.
40 changes: 34 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,24 +111,52 @@ A basic example may then look like this:

### Other Ways To Make a LocusZoom Plot

#### Using the Standard Layout
#### Use a Predefined Layout

The core LocusZoom library comes equipped with a standard layout. See what its contents are by inspecting `LocusZoom.StandardLayout`.
The core LocusZoom library comes equipped with several predefined layouts, organized by type ("plot", "panel", "data_layer", and "dashboard"). You can see what layouts are predefined by reading the contents of `assets/js/app/Layouts.js` or in the browser by entering `LocusZoom.Layouts.list()` (or to list one specific type: `LocusZoom.Layouts.list(type)`).

If no layout is passed to `LocusZoom.populate()` the standard layout is used (requiring appropriate data sources to be configured).
Get any predefined layout by type and name using `LocusZoom.Layouts.get(type, name)`.

If no layout is passed to `LocusZoom.populate()` the Standard GWAS plot layout is used (requiring appropriate data sources to be configured).

If your use case is similar to the standard layout with only minor changes, use `LocusZoom.mergeLayouts()` to define your layout (rather than editing `LocusZoom.StandardLayout`). This method takes two arguments in order - the first is your customizations, and the second is your "base" or default layout. So, to simply change the width of the standard layout it might look like this:
#### Build a Layout Using Some Predefined Pieces

`LocusZoom.Layouts.get(type, name)` can also be used to pull predefined layouts of smaller pieces, like data layers or dashboards, into a custom layout:

```javascript
var layout = LocusZoom.mergeLayouts({ width: 1000 }, LocusZoom.StandardLayout);
var layout = {
width: 1000,
height: 500,
panels: [
LocusZoom.Layouts.get("panel","gwas"),
{
id: "custom_panel",
...
},
LocusZoom.Layouts.get("panel","genes")
]
...
};
```

#### Modify a Predefined Layout

The `get()` function also accepts a partial layout to be merged with the predefined layout as a third argument, providing the ability to use predefined layouts as starting points for custom layouts with only minor differences. Example:

```javascript
var changes = {
label_font_size: 20,
transition: false
};
LocusZoom.Layouts.get("data_layer", "genes", changes);
```

#### Predefining State by Building a State Object

**State** is a serializable JSON object that describes orientation to specific data from data sources, and specific interactions with the layout. This can include a specific query against various data sources or pre-selecting specific elements. Essentially, the state object is what tracks these types of user input under the hood in LocusZoom, and it can be predefined at initialization as a top-level parameter in the layout. For example:

```javascript
var layout = LocusZoom.mergeLayouts({ state: { chr: 6, start: 20379709, end: 20979709 } }, LocusZoom.StandardLayout);
var layout = LocusZoom.Layouts.merge({ state: { chr: 6, start: 20379709, end: 20979709 } }, LocusZoom.StandardLayout);
```

#### Predefining State With `data-region`
Expand Down
2 changes: 1 addition & 1 deletion assets/js/app/DataLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ LocusZoom.DataLayer = function(layout, parent) {
this.parent_plot = null;
if (typeof parent != "undefined" && parent instanceof LocusZoom.Panel){ this.parent_plot = parent.parent; }

this.layout = LocusZoom.mergeLayouts(layout || {}, LocusZoom.DataLayer.DefaultLayout);
this.layout = LocusZoom.Layouts.merge(layout || {}, LocusZoom.DataLayer.DefaultLayout);
if (this.layout.id){ this.id = this.layout.id; }

// Ensure any axes defined in the layout have an explicit axis number (default: 1)
Expand Down
2 changes: 1 addition & 1 deletion assets/js/app/DataLayers/genes.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ LocusZoom.DataLayers.add("genes", function(layout){
track_vertical_spacing: 10,
hover_element: "bounding_box"
};
layout = LocusZoom.mergeLayouts(layout, this.DefaultLayout);
layout = LocusZoom.Layouts.merge(layout, this.DefaultLayout);

// Apply the arguments to set LocusZoom.DataLayer as the prototype
LocusZoom.DataLayer.apply(this, arguments);
Expand Down
2 changes: 1 addition & 1 deletion assets/js/app/DataLayers/line.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ LocusZoom.DataLayers.add("line", function(layout){
y_axis: { field: "y", axis: 1 },
hitarea_width: 5
};
layout = LocusZoom.mergeLayouts(layout, this.DefaultLayout);
layout = LocusZoom.Layouts.merge(layout, this.DefaultLayout);

// Var for storing mouse events for use in tool tip positioning
this.mouse_event = null;
Expand Down
2 changes: 1 addition & 1 deletion assets/js/app/DataLayers/scatter.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ LocusZoom.DataLayers.add("scatter", function(layout){
},
id_field: "id"
};
layout = LocusZoom.mergeLayouts(layout, this.DefaultLayout);
layout = LocusZoom.Layouts.merge(layout, this.DefaultLayout);

// Extra default for layout spacing
// Not in default layout since that would make the label attribute always present
Expand Down
61 changes: 50 additions & 11 deletions assets/js/app/Layouts.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ LocusZoom.Layouts = (function() {
return null;
} else if (layouts[type][name]) {
if (typeof modifications == "object"){
return LocusZoom.mergeLayouts(modifications, layouts[type][name]);
return LocusZoom.Layouts.merge(modifications, layouts[type][name]);
} else {
return JSON.parse(JSON.stringify(layouts[type][name]));
}
Expand Down Expand Up @@ -47,12 +47,51 @@ LocusZoom.Layouts = (function() {

obj.list = function(type) {
if (!layouts[type]){
return Object.keys(layouts);
var list = {};
Object.keys(layouts).forEach(function(type){
list[type] = Object.keys(layouts[type]);
});
return list;
} else {
return Object.keys(layouts[type]);
}
};

// Merge any two layout objects
// Primarily used to merge values from the second argument (the "default" layout) into the first (the "custom" layout)
// Ensures that all values defined in the second layout are at least present in the first
// Favors values defined in the first layout if values are defined in both but different
obj.merge = function (custom_layout, default_layout) {
if (typeof custom_layout != "object" || typeof default_layout != "object"){
throw("LocusZoom.Layouts.merge only accepts two layout objects; " + (typeof custom_layout) + ", " + (typeof default_layout) + " given");
}
for (var property in default_layout) {
if (!default_layout.hasOwnProperty(property)){ continue; }
// Get types for comparison. Treat nulls in the custom layout as undefined for simplicity.
// (javascript treats nulls as "object" when we just want to overwrite them as if they're undefined)
// Also separate arrays from objects as a discrete type.
var custom_type = custom_layout[property] == null ? "undefined" : typeof custom_layout[property];
var default_type = typeof default_layout[property];
if (custom_type == "object" && Array.isArray(custom_layout[property])){ custom_type = "array"; }
if (default_type == "object" && Array.isArray(default_layout[property])){ default_type = "array"; }
// Unsupported property types: throw an exception
if (custom_type == "function" || default_type == "function"){
throw("LocusZoom.Layouts.merge encountered an unsupported property type");
}
// Undefined custom value: pull the default value
if (custom_type == "undefined"){
custom_layout[property] = JSON.parse(JSON.stringify(default_layout[property]));
continue;
}
// Both values are objects: merge recursively
if (custom_type == "object" && default_type == "object"){
custom_layout[property] = LocusZoom.Layouts.merge(custom_layout[property], default_layout[property]);
continue;
}
}
return custom_layout;
};

return obj;
})();

Expand Down Expand Up @@ -285,7 +324,7 @@ LocusZoom.Layouts.add("data_layer", "genome_legend", {
Dashboard Layouts
*/

LocusZoom.Layouts.add("dashboard", "panel", {
LocusZoom.Layouts.add("dashboard", "standard_panel", {
components: [
{
type: "remove_panel",
Expand All @@ -303,7 +342,7 @@ LocusZoom.Layouts.add("dashboard", "panel", {
]
});

LocusZoom.Layouts.add("dashboard", "plot", {
LocusZoom.Layouts.add("dashboard", "standard_plot", {
components: [
{
type: "title",
Expand Down Expand Up @@ -343,7 +382,7 @@ LocusZoom.Layouts.add("panel", "gwas", {
proportional_origin: { x: 0, y: 0 },
margin: { top: 35, right: 50, bottom: 40, left: 50 },
inner_border: "rgba(210, 210, 210, 0.85)",
dashboard: LocusZoom.Layouts.get("dashboard", "panel"),
dashboard: LocusZoom.Layouts.get("dashboard", "standard_panel"),
axes: {
x: {
label_function: "chromosome",
Expand Down Expand Up @@ -392,7 +431,7 @@ LocusZoom.Layouts.add("panel", "genes", {
scroll_to_zoom: true,
x_linked: true
},
dashboard: LocusZoom.Layouts.get("dashboard", "panel"),
dashboard: LocusZoom.Layouts.get("dashboard", "standard_panel"),
data_layers: [
LocusZoom.Layouts.get("data_layer", "genes")
]
Expand Down Expand Up @@ -884,32 +923,32 @@ LocusZoom.Layouts.add("panel", "genome_legend", {
Plot Layouts
*/

LocusZoom.Layouts.add("plot", "gwas_standard", {
LocusZoom.Layouts.add("plot", "standard_gwas", {
state: {},
width: 800,
height: 450,
resizable: "responsive",
aspect_ratio: (16/9),
min_region_scale: 20000,
max_region_scale: 4000000,
dashboard: LocusZoom.Layouts.get("dashboard", "plot"),
dashboard: LocusZoom.Layouts.get("dashboard", "standard_plot"),
panels: [
LocusZoom.Layouts.get("panel", "gwas"),
LocusZoom.Layouts.get("panel", "genes")
]
});

// Shortcut to "StandardLayout" for backward compatibility
LocusZoom.StandardLayout = LocusZoom.Layouts.get("plot", "gwas_standard");
LocusZoom.StandardLayout = LocusZoom.Layouts.get("plot", "standard_gwas");

LocusZoom.Layouts.add("plot", "phewas_standard", {
LocusZoom.Layouts.add("plot", "standard_phewas", {
width: 800,
height: 500,
min_width: 800,
min_height: 500,
responsive_resize: true,
aspect_ratio: 1.6,
dashboard: LocusZoom.Layouts.get("dashboard", "plot"),
dashboard: LocusZoom.Layouts.get("dashboard", "standard_plot"),
panels: [
LocusZoom.Layouts.get("panel", "phewas"),
LocusZoom.Layouts.get("panel", "genome_legend"),
Expand Down
37 changes: 1 addition & 36 deletions assets/js/app/LocusZoom.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ LocusZoom.populate = function(selector, datasource, layout, state) {
console.warn("Warning: state passed to LocusZoom.populate as fourth argument. This behavior is deprecated. Please include state as a parameter of layout");
var stateful_layout = { state: state };
var base_layout = layout || {};
layout = LocusZoom.mergeLayouts(stateful_layout, base_layout);
layout = LocusZoom.Layouts.merge(stateful_layout, base_layout);
}
var plot;
d3.select(selector).call(function(){
Expand Down Expand Up @@ -257,41 +257,6 @@ LocusZoom.createCORSPromise = function (method, url, body, headers, timeout) {
return response.promise;
};

// Merge two layout objects
// Primarily used to merge values from the second argument (the "default" layout) into the first (the "custom" layout)
// Ensures that all values defined in the second layout are at least present in the first
// Favors values defined in the first layout if values are defined in both but different
LocusZoom.mergeLayouts = function (custom_layout, default_layout) {
if (typeof custom_layout != "object" || typeof default_layout != "object"){
throw("LocusZoom.mergeLayouts only accepts two layout objects; " + (typeof custom_layout) + ", " + (typeof default_layout) + " given");
}
for (var property in default_layout) {
if (!default_layout.hasOwnProperty(property)){ continue; }
// Get types for comparison. Treat nulls in the custom layout as undefined for simplicity.
// (javascript treats nulls as "object" when we just want to overwrite them as if they're undefined)
// Also separate arrays from objects as a discrete type.
var custom_type = custom_layout[property] == null ? "undefined" : typeof custom_layout[property];
var default_type = typeof default_layout[property];
if (custom_type == "object" && Array.isArray(custom_layout[property])){ custom_type = "array"; }
if (default_type == "object" && Array.isArray(default_layout[property])){ default_type = "array"; }
// Unsupported property types: throw an exception
if (custom_type == "function" || default_type == "function"){
throw("LocusZoom.mergeLayouts encountered an unsupported property type");
}
// Undefined custom value: pull the default value
if (custom_type == "undefined"){
custom_layout[property] = JSON.parse(JSON.stringify(default_layout[property]));
continue;
}
// Both values are objects: merge recursively
if (custom_type == "object" && default_type == "object"){
custom_layout[property] = LocusZoom.mergeLayouts(custom_layout[property], default_layout[property]);
continue;
}
}
return custom_layout;
};

// Validate a (presumed complete) state object against internal rules for consistency
// as well as any layout-defined constraints
LocusZoom.validateState = function(new_state, layout){
Expand Down
2 changes: 1 addition & 1 deletion assets/js/app/Panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ LocusZoom.Panel = function(layout, parent) {
this.svg = {};

// The layout is a serializable object used to describe the composition of the Panel
this.layout = LocusZoom.mergeLayouts(layout || {}, LocusZoom.Panel.DefaultLayout);
this.layout = LocusZoom.Layouts.merge(layout || {}, LocusZoom.Panel.DefaultLayout);

// Define state parameters specific to this panel
if (this.parent){
Expand Down
4 changes: 2 additions & 2 deletions assets/js/app/Plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ LocusZoom.Plot = function(id, datasource, layout) {
// If no layout was passed, use the Standard GWAS Layout
// Otherwise merge whatever was passed with the Default Layout
if (typeof layout == "undefined"){
this.layout = LocusZoom.mergeLayouts({}, LocusZoom.Layouts.get("plot", "gwas_standard"));
this.layout = LocusZoom.Layouts.merge({}, LocusZoom.Layouts.get("plot", "standard_gwas"));
} else {
this.layout = layout;
}
LocusZoom.mergeLayouts(this.layout, LocusZoom.Plot.DefaultLayout);
LocusZoom.Layouts.merge(this.layout, LocusZoom.Plot.DefaultLayout);

// Create a shortcut to the state in the layout on the Plot
this.state = this.layout.state;
Expand Down
2 changes: 1 addition & 1 deletion demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ <h3>Top Hits</h3>
var layout = {
resizable: "manual"
};
layout = LocusZoom.mergeLayouts(layout, LocusZoom.StandardLayout);
layout = LocusZoom.Layouts.merge(layout, LocusZoom.StandardLayout);

// Populate the div with a LocusZoom plot using the default layout
var plot = LocusZoom.populate("#lz-1", data_sources, layout);
Expand Down
2 changes: 1 addition & 1 deletion demo_responsive.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ <h3 style="display: inline; padding-right: 30px;">LocusZoom Responsive Demo</h3>
.add("sig", ["StaticJSON", [{ "x": 0, "y": 7.30103 }, { "x": 2881033286, "y": 7.30103 }] ]);

// Populate the div with a LocusZoom plot
var layout = LocusZoom.mergeLayouts({ responsive_resize: true }, LocusZoom.StandardLayout);
var layout = LocusZoom.Layouts.merge({ responsive_resize: true }, LocusZoom.StandardLayout);
var plot = LocusZoom.populate("#lz-1", data_sources, layout);

/**********************************************************************************
Expand Down
1 change: 1 addition & 0 deletions files.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// Test suites. Should be able to be executed in any order.
var test_suite = [
"./test/LocusZoom.js",
"./test/Layouts.js",
"./test/DataLayer.js",
"./test/Singletons.js",
"./test/Dashboard.js",
Expand Down
Loading

0 comments on commit 5d4c33d

Please sign in to comment.